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
var | let | const | |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisted | Yes, as undefined | Yes, in TDZ | Yes, in TDZ |
| Re-declare | Yes | No | No |
| Re-assign | Yes | Yes | No — 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 — var → undefined, 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
| Operation | Time |
|---|---|
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/delete | O(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
- Why does
null >= 0returntruewhilenull == 0returnsfalse? — Relational operators (>=) coercenullto0for comparison purposes, while==special-casesnullto be loosely equal only toundefined, not to numeric0— two different coercion rule sets applying to the same value, which is exactly why this reads as inconsistent. - A loop uses
parseInt(someString)without a radix and behaves unexpectedly on a specific input. — Strings with a leading0were historically parsed as octal in older engines (parseInt("08")could return0, since8isn'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. - Why is
WeakMapthe right structure for a per-object cache, instead of a regularMap? — A regularMapholds a strong reference to its keys, meaning objects used as keys can never be garbage collected as long as the Map exists —WeakMapholds 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]]