Topics in this subject
JavaScript 3 min read Updated 4 Aug 2026

Part 20 — Advanced JavaScript

Execution context & the call stack, Memory, garbage collection, weak references, Symbols & well-known symbols, Iterators & generators, Tail calls

Execution context & the call stack#

Each function call pushes an execution context (with its variable environment, scope chain, and this) onto the call stack. Returning pops it. A stack overflow is too many nested contexts (usually runaway recursion).

graph TD
    subgraph "Call Stack (top runs)"
    c["multiply() context"]
    b["square() context"]
    a["main() / global context"]
    end
    c --> b --> a

Memory, garbage collection, weak references#

JS uses a mark-and-sweep GC: starting from roots (global, stack), it marks everything reachable; the unmarked is swept. You can't force GC. Leaks happen when you unintentionally keep references: forgotten timers, detached DOM nodes held in a variable, growing caches, closures over big data, and listeners never removed.

WeakMap/WeakSet/WeakRef/FinalizationRegistry hold objects weakly so they don't block collection — use for caches and metadata keyed by object.

Symbols & well-known symbols#

const id = Symbol("id");             // unique, non-colliding key
obj[id] = 1;                          // hidden from normal enumeration
Symbol.for("x") === Symbol.for("x"); // true — global registry
// well-known symbols customize built-in behaviour:
class Range {
  constructor(a,b){this.a=a;this.b=b;}
  *[Symbol.iterator]() { for (let i=this.a;i<=this.b;i++) yield i; }
}
[...new Range(1,3)];                  // [1,2,3]

Iterators & generators#

// iterator protocol: an object with next() → { value, done }
function makeIter(arr) {
  let i = 0;
  return { next: () => i < arr.length ? {value:arr[i++],done:false} : {value:undefined,done:true},
           [Symbol.iterator]() { return this; } };
}

// generators produce iterators effortlessly and can pause/resume
function* fib() { let [a,b]=[0,1]; while(true){ yield a; [a,b]=[b,a+b]; } }
const g = fib(); g.next().value; // 0 → 1 → 1 → 2 ...

// generators can receive values and delegate
function* outer(){ yield* [1,2]; const x = yield 3; return x; }

Async generators & iterators:

async function* pages(url) {
  let next = url;
  while (next) {
    const res = await fetch(next); const data = await res.json();
    yield data.items;
    next = data.nextUrl;
  }
}
for await (const items of pages("/api")) process(items);

Tail calls#

The spec defines proper tail calls (a call in tail position reuses the stack frame), but V8 does not implement it. So don't rely on TCO in Node/Chrome; convert deep recursion to loops.

Decorators (Stage 3 proposal) 🟡#

function logged(value, ctx) {
  return function (...args) { console.log(`call ${ctx.name}`); return value.call(this, ...args); };
}
class Api { @logged fetchData() {} }

Available today via TypeScript/Babel; native support is arriving. Used for cross-cutting concerns (logging, memo, validation, DI).

Engine performance internals 🎯#

  • Hidden classes (shapes/maps): V8 assigns objects an internal "shape" describing their properties. Objects created with the same properties in the same order share a shape → fast property access via offsets.
  • Inline caches (ICs): V8 caches where a property lives for a given shape at each access site. Consistent shapes keep the IC "monomorphic" (fastest); mixed shapes make it "polymorphic" or "megamorphic" (slow).
  • Deoptimization: if your assumptions break (a function suddenly gets a different type, an object changes shape mid-loop, you add properties dynamically), V8 throws away optimized code and falls back to bytecode.

Practical rules for fast code:

  1. Initialize all object properties in the constructor, in a consistent order — don't add properties later.
  2. Don't mix types in an array or a function's parameters if it's hot.
  3. Avoid delete on hot objects (deoptimizes shape); set to null or use a Map.
  4. Keep functions monomorphic (same argument shapes/types).
  5. Avoid arguments leaking; use rest params.

Interview questions (Part 20):

  1. Explain mark-and-sweep GC and name three common leak sources.
  2. What are hidden classes and inline caches, and how do they affect performance?
  3. Difference between an iterator and a generator; write a generator for the Fibonacci sequence.
  4. Does JS optimize tail calls? What are the implications?
  5. What are Symbols for? Give a well-known symbol example.