Topics
v5.0.0
On this page · sections

JavaScript Memory & Garbage Collection

Can you explain why an object is still alive — and which frontend patterns accidentally keep it that way?

JavaScript does not expose free(). GC collects unreachable objects. Leaks are extra reachability: listeners, caches, closures, detached DOM, timers.

These questions test:

  • Reachability from roots
  • Common SPA leak patterns
  • Why WeakMap exists
  • Closures vs retained graphs
  • What you can (and cannot) measure in DevTools

Questions

GC

  1. Reachability and Roots
  2. Common Leak Patterns

Tools

  1. WeakMap as Non-Owning Storage
  2. Closures and Accidental Retention

Senior-Level Follow-ups

  1. Detached DOM
  2. Can You Force GC?
  3. What I’d Profile

GC

1. Reachability and Roots

Why This Matters

“The variable went out of scope” is not enough. Another path may still point at the object.

Problem

When is an object eligible for collection?

Example

Roots → global / module bindings → current stack locals → host handles (DOM, timers, registered listeners)

If a path exists from a root, the object stays.

function make() { const big = new Array(1e6).fill("x"); return () => big.length; } let hold = make();

big lives as long as hold does.

Engines use mark-and-sweep (with generational / incremental variants). Young objects die fast; survivors are promoted.

Interview Answer

“GC follows reachability from roots. I reason in graphs: who still points at this? Collection is nondeterministic. Clearing the last reference makes the object eligible, not immediately freed.”

2. Common Leak Patterns

Why This Matters

SPAs never unload the page. A leak per navigation is a crash over a workday.

Problem

Name the leaks you actually see in frontend apps.

Example

Forgotten listeners

window.addEventListener("resize", onResize); // missing removeEventListener

Timers

setInterval(poll, 5000); // missing clearInterval

Global / module caches

const cache = new Map(); cache.set(user, hugePayload);

Subscriptions

Redux, WebSocket, and subscribe() without unsubscribe.

Closures in long-lived callbacks that capture large trees.

Interview Answer

“Leaks are retained references. I look at listeners, intervals, module Maps, and subscriptions first. Each mount needs a matching cleanup. Caches need bounds or WeakMap keys.”

Tools

3. WeakMap as Non-Owning Storage

Problem

When does WeakMap fix a leak that Map cannot?

Example

const extra = new WeakMap(); extra.set(node, computed); node.remove(); // node can be collected; extra entry goes with it

If you need to iterate the cache, WeakMap cannot help. Use a Map plus explicit delete or a bounded LRU — LRU Cache.

WeakRef and FinalizationRegistry exist but are easy to misuse. Prefer WeakMap for metadata.

Interview Answer

“WeakMap keys are weak. I use it for per-object metadata whose lifetime should match the object. If I need listing, eviction, or persistence, I use a strong Map with a policy.”

4. Closures and Accidental Retention

Problem

Does returning a small function keep a large sibling binding alive?

Example

function load() { const huge = fetchPayload(); const id = huge.id; return () => id; }

Spec-wise the environment can retain huge. Engines may optimize if huge is unused. Do not rely on it.

If the returned function logs huge, or a method on a retained object closes over it, it stays.

Split the factory so the large data is not in the same environment as the long-lived callback.

Interview Answer

“A closure keeps its environment reachable. I assume unused locals can still pin memory unless I have measured otherwise. I do not close over large objects in handlers that outlive a view.”

Senior-Level Follow-ups

5. Detached DOM

const node = document.getElementById("panel"); window.cache = node; node.remove();

The node is out of the document but still alive. Its listeners and child graph stay too.

Null the cache. Prefer WeakMap keyed by the node if you must store extras.

6. Can You Force GC?

Not in portable JS. Chrome DevTools can collect; Node --expose-gc plus global.gc() is a test hook.

global.gc is not an application API.

7. What I’d Profile

Heap snapshot → compare retained size.

Allocation timeline → who allocates in a click handler.

Look for:

  • Detached HTMLElement growing
  • Listener count growing
  • A Map whose size only increases

Fix the reference. Do not “tune GC.”

Final Interview Strategy

1. Alive = reachable from a root 2. SPAs make leaks permanent 3. Pair every subscribe with unsubscribe 4. WeakMap for object metadata 5. Closures retain environments 6. Detached DOM is still a graph 7. Measure with heap snapshots, don't guess

Senior-level signal: Name the retaining path. GC trivia without a leak story is not a senior answer.

Last updated on