Detailed explanation
A memory leak in JavaScript occurs when memory that an application no longer needs remains reachable and therefore cannot be reclaimed by garbage collection. JavaScript manages allocation and cleanup automatically, but the runtime cannot decide whether a reachable object is still useful to your application. Leaks matter especially in long-running browser tabs and Node.js services: retained objects can accumulate, increase garbage-collection work, slow execution, and eventually exhaust available memory.
Modern JavaScript engines use tracing garbage collectors, commonly based on marking reachable objects and reclaiming unreachable ones. Reachability starts from roots such as global objects and active execution contexts, and follows references through properties, collections, and closures. Global variables, module-level caches, and arrays that keep growing can therefore retain entire object graphs. Circular references alone are not a leak: if a cycle is unreachable from all roots, a tracing collector can collect it. The important question is not whether objects reference each other, but whether something live still references them.
Forgotten timers and event listeners are common causes because they can keep callbacks and the data those callbacks reference alive. For example, const id = setInterval(tick, 1000) schedules repeated work until clearInterval(id) cancels it; it becomes a leak when that work and its retained state outlive their intended owner. Likewise, a listener on window or document may retain component state after the component disappears. Remove it with removeEventListener using the same event type, callback reference, and capture setting, or register it with an AbortSignal and abort during cleanup. A detached DOM node can also remain alive when a JavaScript reference still points to it. However, removing a node does not automatically mean it leaks.
Closures can retain large objects when their functions stay reachable and still need those objects. Unbounded Maps, subscriptions, and caches create similar retention problems. Prevent leaks by tying cleanup to lifecycle boundaries, limiting cache size, and releasing obsolete references. Interviewers value this reachability-based explanation over simply listing suspicious APIs. For diagnosis, repeat an operation and its cleanup, compare heap snapshots, and inspect retaining paths for objects that persist unexpectedly; a temporary memory spike alone does not prove a leak.
Key points to remember
- A leak is unwanted retention of reachable memory, not a failure to manually free objects.
- Globals, module-level collections, and unbounded caches can retain large object graphs.
- Timers, subscriptions, and listeners need cleanup when their owning feature stops using them.
- Detached DOM nodes and closure-captured data leak only when unwanted references keep them reachable.
- Use repeated lifecycle tests, heap snapshots, and retaining paths to investigate persistent growth.
Common mistakes candidates make
- Claiming that circular references always leak or that automatic garbage collection prevents every leak.
- Calling removeEventListener with a new callback function instead of the registered callback reference.
- Treating every closure, detached node, or memory spike as proof of a leak without checking reachability.
Likely follow-up questions
Does an event listener on a detached DOM node always cause a leak?
No. If the node, its listener, and any associated objects are unreachable from live roots, they can be collected together. Retention becomes a problem when something live, such as a global collection, still references them.
Can WeakMap prevent memory leaks?
WeakMap does not keep its keys alive by itself, making it useful for metadata associated with objects whose lifetime is controlled elsewhere. It does not solve retention through other strong references or replace explicit timer and subscription cleanup.
How would you confirm a suspected memory leak?
Repeat a representative action, such as mounting and removing a component, and compare heap snapshots after cleanup and garbage collection. Inspect unexpectedly surviving objects and their retaining paths to find the reference preventing reclamation.
Interview tip: Start with reachability, explain one timer or listener example and its cleanup, then describe how heap snapshots reveal the retaining reference.
