Cheat sheet · JavaScript Core Mastery

JavaScript Core cheat sheet

How JavaScript really runs: execution contexts, the call stack, hoisting, scope, closures, the engine and the event loop. Built for the night before an interview. The same model explains most traps.

Read the full series →

blogs · jp-blogs.vercel.app/cheatsheets/series/javascript

Execution contextLesson 1 →

  • Everything runs inside an execution context: a memory half (variable environment, key to value pairs) and a code half (thread of execution).
  • The global context is created first. Every function call makes a new context, deleted when the call ends.
  • Phase 1, memory creation: var gets undefined, a function declaration gets its whole body. Phase 2, code execution: line by line.
console.log(a);        // undefined
console.log(greet());  // "hello"
var a = 10;
function greet() { return "hello"; }

Memory phase explains why both lines run without errors.

Tip: JavaScript is synchronous and single-threaded: one stack, one command at a time. Async work happens outside the engine (Lesson 14).

Call stackLesson 2 →

  • A stack (LIFO): push a context on a call, pop it when the function finishes (last line or return).
  • The global context is at the bottom and is popped once, at the very end.
  • Same function called 3 times = 3 separate contexts that share nothing.
  • Stack trace: read top to bottom. The top is the deepest, most recent call.
function f() { return f(); }
f();   // no stop condition
// RangeError: Maximum call stack
// size exceeded

A stack overflow: recursion with no stopping point.

var, let, const and the TDZLesson 3 →

Topicvarletconst
ScopeFunctionBlockBlock
HoistedYes, as undefinedYes, but locked (TDZ)Yes, but locked (TDZ)
RedeclareAllowedSyntaxErrorSyntaxError
ReassignYesYesNo (TypeError)
On windowYesNoNo
  • TDZ (temporal dead zone): from the top of the scope until the declaration runs. Reading throws ReferenceError: Cannot access before initialization.
  • const locks the binding, not the value: obj.x = 1 still works. Object.freeze is shallow.
  • let and const live in a declarative record, var in the global object. That is why let stopped global name clashes.

Function scope vs block scopeLesson 4 →

  • Each call gets its own variable environment: two functions with var x never clash.
  • Access goes outward through the outer reference, never sideways. Parameters are set in the memory phase.
  • After a function returns, its environment is freed, unless a closure still holds it.
for (var i = 0; i < 3; i++) {}
console.log(i);   // 3, var ignores block
for (let j = 0; j < 3; j++) {}
console.log(j);   // ReferenceError

let makes a fresh binding per iteration and discards them.

The global object and thisLesson 5 →

WhereGlobal object
Browserwindow
Node.jsglobal
Web Workerself
AnywhereglobalThis
  • Even an empty file gets a global context, a global object and this. At top level of a classic script this === window (undefined in an ES module).
  • Two script tags share one window: a second var count overwrites the first.
  • 'use strict' does not change top-level this. It only makes this undefined in a plain function call.

undefined, null and not definedLesson 6 →

TermMeaning
undefinedMemory reserved, no value yet (set by the engine)
nullEmpty on purpose (set by you)
not definedNever declared: ReferenceError
  • Falsy values: undefined, null, 0 (and -0), 0n, "", false, NaN.
  • typeof undeclared returns "undefined" without throwing.
  • JavaScript is loosely (dynamically) typed: the type belongs to the value, not the variable.
  • Never assign undefined yourself. Use null for "intentionally empty".

Scope chain and shadowingLesson 7 →

  • A lexical environment = local memory + a link to its parent. Global links to null.
  • Lookup goes inside out until found. Not found anywhere: ReferenceError.
  • JavaScript is lexically (statically) scoped: scope depends on where code is written, not who calls it.
  • Shadowing: a local name hides an outer one. The outer value is unchanged.
  • Global can never reach into a function's locals.
const x = "global";
function a() {
  const x = "local";   // shadows
  function b() { return x; }
  return b();           // "local"
}

TDZ details and the five errorsLesson 8 →

  • The TDZ is about time, not position: a function above the declaration works if it is called after the declaration ran.
  • Shadowing: an inner let x has its own TDZ, so the outer x is never reached.
  • function f(a = b, b = 1) throws: defaults run left to right like let. typeof is safe on undeclared names but throws in the TDZ.
  • A SyntaxError is found at parse time, so nothing in the file runs.
MessageCause
x is not definedNever declared
Cannot access 'x' before initializationInside the TDZ
Identifier 'x' has already been declaredRedeclared let or const
Missing initializer in const declarationconst x;
Assignment to constant variableReassigned const

Blocks and shadowing rulesLesson 9 →

  • A block (if, loop, bare braces) creates a new lexical environment only if it holds let, const, class or a function declaration.
  • var ignores blocks, so a var "shadow" is the same variable reassigned.
  • switch has one block for all cases: two let rate in different cases throw. Wrap each case body in braces.
OuterInnerResult
let xlet x in a blockLegal shadowing
let xvar x in a blockSyntaxError
let xvar x in a functionLegal
Parameter alet a at top of bodySyntaxError

ClosuresLesson 10 →

  • A closure = a function plus a live reference to the lexical environment where it was created.
  • It holds the variable (a reference), not a copy of the value.
  • Each call of the outer function makes a separate closure with its own variables.
  • Uses: private state, factories, callbacks, once and memo helpers.
  • A leak happens if something (a never-removed listener) keeps the closure alive.
function counter() {
  let n = 0;
  return () => ++n;   // keeps n alive
}
const c = counter();
c(); c();             // 1, 2

The loop and setTimeout bugLesson 11 →

for (var i = 1; i <= 3; i++) {
  setTimeout(() => log(i), i * 1000);
}
// 4 4 4  (one shared i, loop already done)

All callbacks close over one variable.

FixHow
letFresh binding per iteration
IIFE(function (j) { ... })(i) copies the value
Helper functionCall it with i: own environment per call
bindlog.bind(null, i) fixes the argument

Watch out: let only fixes the loop variable. A var inside the loop body is still shared. setTimeout(fn, 1000) means at least 1000 ms.

First-class functions and callbacksLesson 12 →

  • First-class: functions can be assigned, stored, passed and returned like any value.
  • Higher-order function: takes a function, returns one, or both.
  • Sync callback runs before the outer call returns. Async callback runs later (timers, events).
  • A regular-function listener gets this = the element. An arrow function keeps the outer this.
const off = pct => price =>
  price * (100 - pct) / 100;
const d20 = off(20);   // keeps pct

el.addEventListener("click", onClick);
el.removeEventListener("click", onClick);
el.addEventListener("click", f,
  { once: true });

An anonymous listener needs an AbortController signal or once.

Engine and runtimeLesson 13 →

  1. Parse: source to tokens to an AST.
  2. Compile (JIT): Ignition (interpreter) makes bytecode; hot code goes to TurboFan machine code.
  3. Execute: call stack plus memory heap. Orinoco collects garbage.
  • The engine only parses, compiles and runs. setTimeout and fetch come from the runtime (browser or Node).
  • Objects with the same properties in the same order share a hidden class, so access is fast. Different shapes make it slower.
  • Deoptimization: if a function gets a new type or shape, optimized code is dropped and bytecode runs.
  • Mark-and-sweep: mark all reachable objects, free the rest. Cycles are fine. Leaks are things still reachable by accident.

Event loop and queuesLesson 14 →

  • Players: call stack, Web APIs, microtask queue (promises, await), callback queue (timers, events).
  • When the stack is empty, the whole microtask queue drains (including new ones) before one callback task runs. Rendering comes after.
  • Code after await is a microtask. A microtask that keeps queuing microtasks starves timers and painting.
log(1);   // log = console.log
setTimeout(() => log(2));
Promise.resolve().then(() => log(3));
(async () => {
  log(4); await null; log(5);
})();
log(6);
// 1 4 6 3 5 2

Sync first, then microtasks in order, then the timer.

Interview one-linersLesson 1 →

QuestionShort answer
Why does var print undefined?The memory phase reserved it as undefined before any code ran.
Is let hoisted?Yes, but locked in the TDZ until its declaration runs.
undefined vs not defined?Reserved but empty vs never declared (ReferenceError).
What is a closure?A function with a live reference to its creation scope.
Lexical or dynamic scope?Lexical: fixed by where the code is written.
setTimeout 0 vs Promise.then?The promise runs first: microtasks drain before timers.
Does const make a value immutable?No, only the binding. Use Object.freeze (shallow).

A cheat sheet is a reminder, not a teacher. Each card links to the lesson that explains it step by step.