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") // symbolObjects (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 objectThis "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 obj1const doesn't mean immutable
const user = { name: "Ada" };
user.name = "Grace"; // ✅ totally legal
user = {}; // ❌ TypeError, THIS is what const actually preventsconst 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 resulttypeof 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) {
return false;
}
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length
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 reasonEven 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 themundefined 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
nullvsundefinedin 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.
