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)
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):
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 independentlet obj1 = { count: 5 };let obj2 = obj1;obj2.count = 10;console.log(obj1.count); // 10 — objects copy by REFERENCE, obj2 IS obj1
const user = { name: "Ada" };user.name = "Grace"; // ✅ totally legaluser = {}; // ❌ 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.
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.
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.
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.
console.log({ a: 1 } === { a: 1 }); // false — two different objects in memoryconsole.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."
let x;console.log(x); // undefined — declared, never assignedlet y = null; // explicitly "no value", assigned on purposeconsole.log(x == y); // true — loose equality treats them as equivalentconsole.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.
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.
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.