DeepFrontend
Practice ArenaBlogSign in
Go Pro
DeepFrontend

Interview-grade learning paths and hands-on practice for mid-to-senior engineers. Master internals, patterns, and system architecture with runnable code.

All systems operational

Core Courses

  • -JavaScript Internals
  • -React Reconciliation
  • -TypeScript rigor
  • -Next.js Caching
  • -Node.js Event Loop
  • -System Design
  • -Web Security
  • -CSS & Page Layouts

Explore

  • Learning Paths
  • Practice Arena
  • Pricing Options
  • HTML Sitemap

Resources

  • Privacy Policy
  • Terms of Service
  • Email Support

© 2026 DeepFrontend. All rights reserved.

Expert learning environments for web engineering teams.

Home/JavaScript/JavaScript Fundamentals
beginner25 min read·Updated Jun 2025

JavaScript Fundamentals

What JavaScript actually is under the hood — how it runs, what values look like in memory, and the small set of primitives that everything else in the language builds on.

fundamentalsprimitivesvaluestypesorientation

Knowledge Check

5 questions · pass at 70%

0/5
easypredict output

1. What does this log?

code
let a = { x: 1 };
let b = a;
b.x = 2;
console.log(a.x);

Interview Questions

2 questions

Cheatsheet

Download-ready reference
Cheatsheet
7 primitives (immutable, compared/copied BY VALUE):
  string, number, bigint, boolean, undefined, null, symbol

Everything else is an OBJECT (compared/copied BY REFERENCE):
  {}, [], function(){}, new Date(), new Map(), ...

typeof quirks:
  typeof null         → "object"   (historical bug, permanent)
  typeof []            → "object"   (arrays are objects)
  typeof function(){}  → "function" (the one special case)

const = binding cannot be REASSIGNED. Says nothing about mutation:
  const o = {}; o.x = 1;  ✅ legal
  const o = {}; o = {};   ❌ TypeError

Prefer === / !== always. Reach for == only deliberately (see Coercion topic).

null   = intentionally empty (you set it)
undefined = not yet set (the engine's default)

Related topics

ScopeCoercion & Equality (== vs ===)

On this page

25m
Knowledge CheckInterview QuestionsCheatsheet

Concept#

The beginner framing: JavaScript is the language that runs in every web browser and, via Node.js, on servers too. It reads your code top to bottom, executing instructions one at a time — with a few important exceptions you'll build up to over this course (async code, the event loop).

The precise mental model: JavaScript is single-threaded (one thing runs at a time, no true parallel execution of your code), dynamically typed (a variable can hold any type, and that type can change), and interpreted-then-JIT-compiled — modern engines like V8 (Chrome, Node.js) start running your code immediately via an interpreter, then compile the "hot" (frequently run) parts to optimized machine code in the background as it runs. You don't have to think about this compilation step day-to-day, but it's why JavaScript can be both quick to start and fast to run.

The engine-level view — every value in JavaScript is one of exactly two kinds:

Primitives (7 total, immutable, compared and copied by value):

"hello"        // string
42             // number
42n            // bigint
true           // boolean
undefined      // undefined
null           // null
Symbol("id")   // symbol

Objects (everything else — plain objects, arrays, functions, dates, maps, sets — compared and copied by reference):

{ name: "Ada" }        // object
[1, 2, 3]               // array (a specialized object)
function greet() {}     // function (a callable object)
new Date()               // Date object

This "value vs. reference" split is the single most consequential fact in the entire language — it explains why comparing two objects with === almost never works the way beginners expect, why mutating an object passed into a function affects the caller's copy, and why const doesn't mean "unchangeable" the way people assume.

let a = 5;
let b = a;
b = 10;
console.log(a); // 5 — primitives copy by value, b is independent
 
let obj1 = { count: 5 };
let obj2 = obj1;
obj2.count = 10;
console.log(obj1.count); // 10 — objects copy by REFERENCE, obj2 IS obj1

const doesn't mean immutable#

const user = { name: "Ada" };
user.name = "Grace"; // ✅ totally legal
user = {};            // ❌ TypeError — THIS is what const actually prevents

const only prevents reassigning the binding — pointing the variable at a different value entirely. It says nothing about whether the value itself can be mutated. For true immutability, you need Object.freeze() (shallow) or to avoid mutation by convention/tooling.

Type coercion, briefly#

JavaScript will often convert values between types automatically — this is powerful but is also the source of the language's most infamous quirks ([] == false is true). This deserves its own deep dive — see Coercion & Equality.


Try It#

Predict the output of each line before running it.

console.log(typeof "hello");
console.log(typeof 42);
console.log(typeof undefined);
console.log(typeof null);
console.log(typeof []);
console.log(typeof {});
console.log(typeof function
Solution
"string"
"number"
"undefined"
"object"     ← famous historical bug, kept for backwards compatibility!
"object"     ← arrays ARE objects
"object"
"function"   ← the one exception: functions get their own typeof result

typeof null === "object" is a bug from JavaScript's very first implementation in 1995 — fixing it now would break too much existing code on the web, so it's permanently preserved. It's one of the most commonly cited "gotchas" in interviews specifically because it's surprising and has a real historical reason behind it.


Implement It Yourself#

Write a deepEqual function that correctly compares two values — handling the primitive-vs-object distinction from this topic:

function deepEqual(a, b) {
  // Primitives (and matching references): straightforward comparison
  if (a === b) return true;
 
  // If either isn't a non-null object at this point, they can't be
  // deeply equal (and we already know a !== b from the check above).
  if (typeof a !== "object" || a === null || typeof b !== "object" || b === null













This mirrors what libraries like Lodash's isEqual or React's dependency-comparison internals do at a basic level — and it only exists because === on objects compares references, not contents.


Common Mistakes#

1. Assuming === compares object contents#

console.log({ a: 1 } === { a: 1 }); // false — two different objects in memory
console.log([1, 2] === [1, 2]);      // false — same reason

Even though they look identical, they're two separate allocations. Use a deep-equality check (or, for arrays/objects you control, compare specific fields) when you actually mean "same contents."

2. Confusing undefined and null#

let x;
console.log(x); // undefined — declared, never assigned
let y = null;    // explicitly "no value", assigned on purpose
console.log(x == y);  // true  — loose equality treats them as equivalent
console.log(x === y); // false — strict equality distinguishes them

undefined generally means "this hasn't been set" (the engine's default). null means "someone deliberately set this to nothing." Mixing them up in API design is a common source of confusing code — pick one convention and stick to it.

3. Mutating an object thinking a copy was made#

function addItem(cart, item) {
  cart.push(item); // mutates the CALLER's array — no copy happened
  return cart;
}

Passing an object or array into a function passes the reference, not a fresh copy. If the caller doesn't expect their data to change, this is a real bug — copy explicitly ([...cart, item]) when you want a new array instead of mutating the original.


Best Practices#

  • Default to const, understanding it protects the binding, not the value's contents.
  • Use === and !== almost always; reach for ==/!= only in the rare, deliberate cases covered in Coercion & Equality.
  • Be explicit about null vs undefined in your own APIs — pick one to mean "intentionally empty" and be consistent.
  • Don't mutate objects/arrays you didn't create, unless that's clearly the function's documented purpose (like Array.prototype.push).

Performance Tips#

  • Primitives are cheap — copying a number or string is a fixed, tiny cost, unlike copying a large object (which V8 never does implicitly; you always get a reference, which is itself cheap — the cost only shows up if YOU explicitly deep-clone).
  • V8 optimizes objects with a stable, predictable shape (the same properties, added in the same order) far better than objects whose shape changes at runtime — this is a deeper topic (hidden classes / Shapes), but the beginner-level takeaway is: initialize all of an object's properties up front where you can, rather than adding them conditionally later.

() {});
) {
return false;
}
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
return keysA.every((key) => deepEqual(a[key], b[key]));
}
deepEqual(1, 1); // true — primitive
deepEqual({ a: 1 }, { a: 1 }); // true — same shape, different references
deepEqual({ a: 1 }, { a: 1 }); // (note: === would say false here!)
deepEqual({ a: { b: 2 } }, { a: { b: 2 } }); // true — recurses into nested objects