The memorizable facts, condensed. Everything here is original material summarizing the
official documentation —
verify details against it before relying on them in production.
Equality and conversion, one rule each
Use === everywhere except the idiomatic x == null, which is true for exactly null and undefined. The rest of this table is what == is hiding.
| Expression | Result and reason |
typeof null | 'object' — a first-release tagging bug; test with value === null. |
typeof NaN | 'number' — NaN is an IEEE-754 value, and the only one not equal to itself. |
null == undefined | true, and both are loosely equal to nothing else — not 0, not false, not "". |
null >= 0 | true, while null > 0 and null == 0 are false: relational operators convert, equality does not. |
[] == false | true — [] becomes "" becomes 0, and false becomes 0. But [] is truthy in an if. |
'5' + 3 | '53' — binary + is the only arithmetic operator that concatenates; '5' - 3 is 2. |
0.1 + 0.2 | 0.30000000000000004 — compare with Number.EPSILON, or store money as integer cents. |
Number(null) | 0, while Number(undefined) is NaN and Number("") is 0. |
Object.is(NaN, NaN) | true. Object.is differs from === only for NaN and for +0 versus -0. |
[NaN].includes(NaN) | true (SameValueZero), while indexOf never finds NaN (strict equality). |
[10, 9, 1].sort() | [1, 10, 9] — the default comparator sorts stringified values; pass (a, b) => a - b. |
1n + 1 | TypeError — BigInt never mixes with Number in arithmetic, though 1n == 1 is true. |
Falsy values and the nullish family
| Value or operator | Behavior |
falsy set | false, 0, -0, 0n, "", null, undefined, NaN — plus the legacy document.all. Nothing else. |
[] and {} | Truthy, both of them. Check .length or Object.keys(o).length instead. |
a || b | Falls through on every falsy a, so a legitimate 0 or "" is replaced. |
a ?? b | Falls through only on null and undefined — the fix for the 0 case. |
a?.b.c | Short-circuits the whole chain to undefined when a is nullish; never yields null. |
a ||= b / a ??= b | Logical assignment; ??= only assigns when a is null or undefined. |
Scope, hoisting and closures
| Concept | What to say in an interview |
var | Function-scoped, hoisted and initialized to undefined; leaks out of blocks. |
let / const | Block-scoped and hoisted but uninitialized — the temporal dead zone throws ReferenceError. |
function decl | Hoisted with its body, so it is callable above its own line; a function expression is not. |
closure | A function plus the live bindings it captured — bindings, not copies of their values. |
for (let i...) | One fresh binding per iteration, which is why the callbacks log 0, 1, 2 instead of 3, 3, 3. |
IIFE | The pre-module way to get a private scope; a module gives every file one for free. |
closure leak | Sibling closures share one context, so any live callback can pin a large captured object. |
fn.length | Parameter count before the first default or rest parameter — the arity libraries inspect. |
this, prototypes and classes
Binding precedence, strongest first: new, then bind, then the call-site receiver, then the default. An arrow function ignores all four and uses the enclosing scope.
| Form | What this is |
obj.fn() | obj — the receiver at the call site, decided by how the function is called. |
const f = obj.fn; f() | undefined in strict mode (all class bodies and modules) — the classic extracted-method bug. |
arrow function | Inherited lexically; arrows also have no arguments, no super and no new.target. |
fn.call / fn.apply | Invoke now with an explicit receiver; apply takes an array of arguments. |
fn.bind(obj) | Returns a new function with this fixed permanently — later call or apply cannot change it. |
new Foo() | A fresh object linked to Foo.prototype, returned unless the body returns another object. |
class field = () => {} | One pre-bound closure per instance; a prototype method is shared but not bound. |
#private | Engine-enforced: outside access is a SyntaxError, and #x in obj is the brand-check guard. |
super.m() | Looks up from the method's [[HomeObject]], not from this — which is why copied mixins lose it. |
Async: ordering, combinators and cancellation
One rule explains most output-order puzzles: the microtask queue drains completely between tasks, and everything before the first await runs synchronously.
| Tool | Behavior |
microtask | Promise handlers and queueMicrotask — run before the next task and before paint. |
task | setTimeout, setInterval, I/O callbacks and events — one per event-loop turn. |
new Promise(fn) | The executor runs synchronously; only the handlers are deferred. |
await x | Yields to the microtask queue even when x is not a promise. |
Promise.all | Rejects on the first rejection; the other promises keep running uncancelled. |
Promise.allSettled | Never rejects — one {status, value} or {status, reason} per input. |
Promise.race | Settles with the first settled input, fulfilled or rejected; the loser is not cancelled. |
Promise.any | First fulfillment wins; rejects with an AggregateError only if all inputs reject. |
AbortController | Pass signal to fetch, then abort() to reject with an AbortError; AbortSignal.timeout(ms) for deadlines. |
forEach + async | Never awaits. Use for...of for sequential work, or Promise.all(items.map(fn)) for parallel. |
fetch + 404 | Fulfills. Only network failures reject — check response.ok before parsing the body. |
TypeScript: narrowing and the type-level toolbox
| Feature | What it does |
unknown | Assignable from anything, usable after a check — the safe any for JSON and catch clauses. |
never | The empty type; assigning the subject to it in a default branch gives an exhaustiveness check. |
x is Foo | A type predicate: the checker trusts it without verifying the body, so keep the body honest. |
as const | Freezes inference to literal types, so mode stays 'dark' instead of widening to string. |
satisfies | Verifies against a type while each property keeps its own inferred type; add as const to keep literals. |
as T | Compile-time only. No runtime check, no conversion; as unknown as T is the escape hatch and the smell. |
keyof / T[K] | Union of keys, and indexed access — together they type a generic property getter. |
typeof value | In a type position, the inferred type of a binding: type Mode = (typeof MODES)[number]. |
Partial / Required | Add or remove ? on every property — shallow, one level only. |
Pick / Omit | Keep or drop listed keys. Omit does not constrain its keys to keyof T, so typos pass silently. |
ReturnType / Awaited | Read a function's return type; compose them for an async payload type. |
T extends U ? A : B | Distributes over unions unless both sides are wrapped in tuples: [T] extends [U]. |
infer U | Captures a type from the matched position — the machinery behind ReturnType and Parameters. |
Modules, iteration and generators
| Feature | Behavior |
ESM import | Static, hoisted, top-level only, and a live binding — the importer sees later updates. |
require | Synchronous, runs at call time, and copies values; no __dirname in ESM (use import.meta.url). |
import() | Returns a promise for the namespace object — the code-splitting boundary bundlers cut on. |
circular ESM | Bindings exist but may be uninitialized: functions survive, const throws from the TDZ. |
top-level await | Defers the module and every importer of it — cheap in config, costly on a critical path. |
module instance | One per resolved specifier: module-level state is a singleton across all importers. |
Symbol.iterator | Makes a value work with for...of and spread; a generator method implements it in one line. |
generator | Lazy iterator: nothing runs until next(), and next(v) passes a value back into the yield. |
yield* | Delegates to another iterable, forwarding next, throw and return as well. |
early break | Calls the iterator's return(), so a generator finally block still runs its cleanup. |
for await...of | Drives an async iterable — the natural shape for paginated APIs and streams. |
iterator helpers | ES2025 .map/.filter/.take on iterators: lazy, so an infinite source stays usable. |
Independent community study resource — not affiliated with or endorsed by Oracle, Microsoft or Ecma International. JavaScript is a trademark of Oracle Corporation; TypeScript is a trademark of Microsoft Corporation. All questions and study notes are original, written from MDN, the ECMAScript specification and the TypeScript handbook. Everything runs in your browser; nothing you answer is stored or transmitted.