Memory Leaks in JavaScript

JavaScript memory leaks occur when unintended references keep objects reachable even though the application no longer needs them. In long-running frontend applications, these retained objects can increase heap usage and eventually cause slower performance, excessive garbage collection, or crashes.

JavaScript uses garbage collection to reclaim objects that are no longer reachable from live roots such as global objects, active execution contexts, and other retained objects. An object does not need to be manually freed; it becomes eligible for collection when nothing reachable refers to it.

A memory leak happens when application logic accidentally preserves a reference to an object that should have become unreachable. Common sources include event listeners that are never removed, timers that continue running, subscriptions that outlive their component, growing global collections, and closures that retain large object graphs.

In frontend applications, component lifecycle mistakes are especially important. For example, repeatedly registering a listener inside a mount or effect without removing it can retain the callback and everything it closes over. A cleanup function should undo resources established by the corresponding setup operation.

Closures themselves are not leaks. They become problematic when a long-lived object retains a closure whose captured variables include data that is no longer needed. Similarly, detached DOM nodes can remain in memory when JavaScript references still point to them, even though they are no longer connected to the document.

Diagnosis usually involves browser DevTools heap snapshots, allocation timelines, and retained-object inspection. A useful workflow is to reproduce the suspected lifecycle repeatedly, compare heap snapshots, identify objects whose counts keep growing, and inspect their retaining paths.

Key points

Interview questions

What is a memory leak in JavaScript?

A memory leak occurs when an application keeps references to objects that it no longer needs. Because those objects remain reachable, the garbage collector cannot reclaim them. Common examples include forgotten event listeners, timers, subscriptions, and unintended global references. Over time, retained memory can increase and affect application performance.

Why can an event listener cause a memory leak?

An event target can retain the registered callback for as long as the listener remains registered. If that callback closes over application data, the retained callback can keep that data reachable as well. Repeatedly registering listeners without removing them can therefore retain multiple callbacks and their captured state. Cleanup should remove listeners that are no longer needed.

Are closures themselves a memory leak?

No. A closure is a normal JavaScript mechanism and its captured variables can be garbage-collected when the closure and its reachable objects are no longer needed. A problem occurs when a long-lived object retains the closure and the closure unnecessarily retains substantial data. The important question is therefore what retains the closure and what the closure retains.

How would you investigate a suspected frontend memory leak?

First, I would create a reproducible sequence that performs the suspected lifecycle repeatedly, such as mounting and unmounting a view. I would inspect heap snapshots before and after repeated cycles and compare retained object counts and sizes. Then I would inspect retaining paths to determine which listener, timer, collection, component, or other object keeps the data reachable. Finally, I would fix the lifecycle ownership and verify that repeated cycles return memory to a stable range rather than continually accumulating.

What frontend patterns commonly cause memory leaks?

Common patterns include registering event listeners without cleanup, leaving intervals or observers active, keeping subscriptions after a component is destroyed, and storing obsolete objects in long-lived arrays or maps. Detached DOM nodes can also be retained by application references. Framework-specific lifecycle errors can make these problems easier to introduce, so resource ownership should be explicit and cleanup should happen when the owning lifecycle ends.