JavaScript · cheat sheet

JavaScript Essentials

Types, scope, this, prototypes, arrays, async and the event loop, modern syntax and number gotchas: the JavaScript facts interviewers expect you to know cold.

Quiz me on JavaScript

Forty-eight facts and snippets, six per topic, for a last review before a JavaScript round.

Types & equality

  • Seven primitives (string, number, bigint, boolean, undefined, symbol, null) plus object. Primitives are immutable and compared by value.
  • typeof null === 'object' is a historic bug; test with x === null. typeof a function is 'function'.
  • == coerces, === does not: '' == 0 is true, null == undefined is true, but null == 0 is false.
  • NaN !== NaN. Use Number.isNaN(x); the global isNaN('abc') coerces first and returns true.
  • Object.is equals === except Object.is(NaN, NaN) is true and Object.is(0, -0) is false.
  • Falsy: false, 0, -0, 0n, '', null, undefined, NaN (and the legacy document.all). Everything else is truthy, including [], {} and '0'.

Scope, hoisting & closures

  • var is function-scoped; let and const are block-scoped.
  • let, const and class are hoisted into the temporal dead zone: using them before the declaration throws ReferenceError.
  • Function declarations are hoisted with their body. With var f = function () {} only f is hoisted (as undefined), so an early call throws TypeError.
  • A closure is a function plus the variables it captured from its enclosing scope; they stay alive as long as the function does (closures note).
  • for (let i ...) creates a fresh i per iteration, so setTimeout(() => console.log(i)) logs 0 1 2; with var it logs 3 3 3.
  • const blocks reassignment, not mutation: const a = []; a.push(1) is fine. Object.freeze is shallow.

this & functions

  • The call site sets this: obj.fn() gives obj; a plain fn() gives undefined in strict mode (the global object in sloppy mode).
  • Arrow functions have no own this, arguments or prototype, and cannot be called with new.
  • fn.call(ctx, a, b) and fn.apply(ctx, [a, b]) invoke immediately; fn.bind(ctx) returns a new function whose this cannot be changed again, even by call.
  • Precedence: new > explicit (call, apply, bind) > implicit (obj.fn()) > default. new even overrides bind (binding rules).
  • Passing a method loses this: setTimeout(obj.fn). Fix with obj.fn.bind(obj) or () => obj.fn().
  • fn.length counts parameters before the first default or rest parameter: ((a, b = 1, c) => {}).length is 1.

Objects & prototypes

  • Property lookup walks the prototype chain: obj → Ctor.prototype → Object.prototype → null.
  • class is mostly sugar over constructor functions: methods live on Class.prototype, class bodies are strict, and calling a class without new throws.
  • Object.create(proto) sets the prototype directly; Object.create(null) makes a dictionary with no inherited keys.
  • { ...obj } and Object.assign() copy own enumerable properties one level deep (a shallow copy).
  • structuredClone() deep-copies Date, Map, Set and cycles, but throws on functions and drops class prototypes.
  • Key order: integer-like keys ascending, then string keys in insertion order. Object.keys({ b: 1, 2: 1, a: 1, 1: 1 }) is ['1', '2', 'b', 'a'].

Arrays & iteration

  • In-place: push, pop, shift, unshift, splice, sort, reverse, fill. Copying (ES2023): toSorted, toReversed, toSpliced, with.
  • Default sort compares strings: [10, 9, 1].sort() is [1, 10, 9]. Pass (a, b) => a - b.
  • forEach cannot break; return only skips to the next item. Use for...of, some or every to stop early.
  • for...in loops over enumerable string keys, inherited ones included; for...of loops over an iterable’s values.
  • ['1', '2', '3'].map(parseInt) is [1, NaN, NaN]: map passes the index as the radix.
  • [NaN].includes(NaN) is true, but [NaN].indexOf(NaN) is -1.

Async & the event loop

  • One call stack: the event loop picks the next task only when the stack is empty.
  • After each task, all microtasks run (promise callbacks, queueMicrotask, MutationObserver) before rendering or the next task (event loop note).
  • The executor in new Promise(executor) runs synchronously; only the .then callbacks are deferred.
  • await x suspends the function and resumes it in a microtask, even if x is not a promise. async functions always return a promise.
  • Promise.all fails fast, allSettled never rejects, race follows the first to settle, any takes the first fulfillment or rejects with AggregateError (write them yourself).
  • The classic ordering puzzle logs 1 4 3 2: synchronous code, then microtasks, then timers.
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
console.log(4);
// 1, 4, 3, 2
JavaScript

Modern syntax

  • a ?? b falls back only on null or undefined; a || b on any falsy value. 0 ?? 5 is 0, 0 || 5 is 5.
  • a?.b?.() short-circuits to undefined when the left side is null or undefined.
  • Destructuring defaults apply only to undefined: const { a = 1 } = { a: null } leaves a as null.
  • [...new Set(arr)] removes duplicates; ...rest collects the remaining items or properties.
  • Map keeps insertion order and takes any key type. WeakMap keys must be objects (or non-registered symbols) and do not prevent garbage collection.
  • ES modules are strict by default, top-level this is undefined, and imports are live, read-only bindings.

Numbers, strings & JSON

  • 0.1 + 0.2 is 0.30000000000000004. Compare with a tolerance, or work in integers (cents).
  • Number.MAX_SAFE_INTEGER is 2 ** 53 - 1. Beyond it use BigInt (10n ** 20n), and never mix: 1n + 1 throws TypeError.
  • +'' is 0, +'3px' is NaN, but parseInt('3px') is 3.
  • + concatenates if either side is a string; other arithmetic converts to numbers. '5' + 2 is '52', '5' - 2 is 3.
  • Strings are immutable: methods return new strings, and str[0] = 'x' throws TypeError in strict mode (it is silently ignored otherwise).
  • JSON.stringify drops undefined, functions and symbols in objects (they become null in arrays), writes NaN as null, and throws on BigInt and cycles.
esc