Back to Notes

JS Core Language & Data Types

Data Types & typeof Gotchas

8 types: Number, BigInt, String, Boolean, Null, Undefined, Symbol (primitives) + Object (reference).

typeof null          // "object" — a famous, unfixable-for-compatibility JS bug; null is NOT an object
typeof []             // "object" — use Array.isArray() to distinguish arrays
typeof NaN            // "number" — NaN is technically a number value
NaN === NaN           // false — use Number.isNaN(NaN) instead

Primitives pass by value; objects/arrays pass by reference — mutating an object parameter inside a function is visible to the caller, mutating a primitive parameter is not.

== vs ===

0 == false      // true  — false coerced to 0
"" == false     // true
null == undefined   // true  — special-cased equal to each other, nothing else
null === undefined  // false

null >= 0    // true  — inconsistent with null == 0 (false) and null > 0 (false)!

=== never coerces types — always prefer it. The null/undefined coercion inconsistency above is a genuinely surprising, real gotcha worth having memorized rather than reasoning through live.

var / let / const

varletconst
ScopeFunctionBlockBlock
HoistedYes, as undefinedYes, in TDZYes, in TDZ
Re-declareYesNoNo
Re-assignYesYesNo — binding immutable, not the value
const arr = [1, 2, 3];
arr.push(4);   // fine — array CONTENTS can still change
arr = [];      // TypeError — the BINDING itself can't be reassigned

Hoisting & the Temporal Dead Zone

JS runs in two phases: creation (memory allocated — varundefined, functions → fully defined, let/const → TDZ) then execution (line by line).

console.log(x);   // undefined — var hoisted, not yet assigned
var x = 5;

console.log(y);   // ReferenceError — y exists but is in the TDZ
let y = 5;

greet();          // "Hello" — function DECLARATIONS are fully hoisted
function greet() { console.log("Hello"); }

hi();             // TypeError — hi is not a function yet (expression, not hoisted as a function)
var hi = function() { console.log("Hi"); };

Array, Map, Set — Complexity to Have Memorized

OperationTime
push() / pop()O(1)
shift() / unshift()O(n) — every remaining element shifts index
map() / filter() / reduce()O(n)
sort()O(n log n)
Map/Set get/set/has/deleteO(1)

Map vs Object: Map keys can be any type (including objects), Map is directly iterable and has .size; Object keys are coerced to strings/Symbols only. WeakMap: keys must be objects, weakly referenced (entries auto-removed when the key is garbage collected), not iterable — the right structure for attaching metadata to an object without preventing its GC (e.g., a memoization cache keyed by object reference).

Common Gotchas Worth Memorizing

parseInt("08")        // 8 in modern engines — but always pass a radix explicitly: parseInt("08", 10)
0.1 + 0.2 === 0.3     // false — floating point representation error (0.30000000000000004)
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON   // the correct equality check for floats

const arr = [1, , 3]; // a genuine "hole", not the same as an explicit undefined
1 in arr              // false — the hole
arr[1]                // undefined — reads as undefined anyway

Interview Drills

  1. Why does null >= 0 return true while null == 0 returns false? — Relational operators (>=) coerce null to 0 for comparison purposes, while == special-cases null to be loosely equal only to undefined, not to numeric 0 — two different coercion rule sets applying to the same value, which is exactly why this reads as inconsistent.
  2. A loop uses parseInt(someString) without a radix and behaves unexpectedly on a specific input. — Strings with a leading 0 were historically parsed as octal in older engines (parseInt("08") could return 0, since 8 isn't a valid octal digit) — modern engines default to base 10, but always passing the radix explicitly (parseInt(str, 10)) removes any engine-version dependency.
  3. Why is WeakMap the right structure for a per-object cache, instead of a regular Map? — A regular Map holds a strong reference to its keys, meaning objects used as keys can never be garbage collected as long as the Map exists — WeakMap holds weak references, so a cached object can still be collected once nothing else references it, and its cache entry is automatically removed.

Cross-links

[[JS Closures & Async]] · [[JS Prototype & OOP]]