Skip to content

Recommended Reading

How to Find and Fix JavaScript Memory Leaks

7 Minutes

How to Find and Fix JavaScript Memory Leaks

Fix Bugs Faster! Log Collection Made Easy

Get started

Memory leaks are the bugs that really creep up on you. One minute your app is running perfectly, then suddenly it slows to a crawl or crashes.

We don’t get a warning with memory leaks, so we need to prepare proactively and be ready when they hit. This means we need to:

  • Know what a leak actually is.
  • Understand the patterns that cause them.
  • Know how to hunt them down with Chrome DevTools, Node.js tooling, and production logging.

This guide is here for all of that. Drawing on our long (and sometimes painful) experience of fixing memory leaks, we’ll give you a process to proactively optimize your app memory and prevent those silent but violent leaks from happening.

What is a JavaScript memory leak?

A JavaScript memory leak is memory that a program no longer needs but the garbage collector can’t reclaim, because something in the code still references it. It’s not a bug that throws an error, so it builds up silently.

Eventually, you’ll notice the following consequences:

  • Growing heap: memory usage climbs and never returns to baseline.
  • Degrading performance: the app slows down the longer it runs.
  • Crashes on constrained devices: phones and low-memory environments force-close the app.
  • Delayed diagnosis: the failure surfaces far from where it originated.

Common causes of memory leaks in JavaScript

Most memory leaks can be traced back to four patterns, often involving poor memory management and inefficient cleanup.

CauseHow it leaks
Lingering event listenersA lingering listener pins its callback and everything it closes over. Common in SPAs where components mount repeatedly. Pair every addEventListener with removeEventListener on teardown.
Closures holding referencesA long-lived closure keeps captured variables alive. A timer or handler closing over a large object holds that object in memory indefinitely. Narrow what the closure captures.
Detached DOM nodesAn element is removed from the page but still referenced by a variable, array, or closure. The node and its whole subtree stay in memory instead of being collected.
Globals and forgotten timersGlobals live for the whole session, and a setInterval that’s never cleared keeps its callback alive forever. Store the interval ID and call clearInterval when done.

How to detect memory leaks with Chrome DevTools

Chrome DevTools has a dedicated Memory panel built for just this purpose. It shows you what’s on the heap, what’s holding references, and how memory grows over time.

The two tools that matter most for javascript memory leak detection are:

  • Heap snapshots: a frozen picture of every object in memory at a single moment.
  • Allocation timeline: a live recording of memory growth as you interact with the app.

Reading a heap snapshot

A heap snapshot captures every object in memory at a single moment, and they’re pretty easy to read if you know how to surface the suspicious objects and follow the retaining path. These steps should help you:

  1. Open the Memory panel: in DevTools, select the Memory tab and choose “Heap snapshot”.
  2. Take a baseline: record a snapshot before triggering the suspect action.
  3. Interact with the app: perform the action you think is leaking.
  4. Take a second snapshot: record again after the interaction.
  5. Compare them: switch the filter to “Objects allocated between snapshots” and inspect what survived.

Look closely for detached DOM nodes and objects that should have been released. The retainers view shows exactly what’s holding each object, pointing you straight at the reference keeping it alive.

Recording the memory timeline

The allocation timeline records memory usage while you interact with the app. Blue bars mark allocations, and bars that never turn grey were never freed. To make a leak visible:

  1. Start a recording: pick “Allocation instrumentation on timeline” in the Memory panel
  2. Repeat one action: perform the same action several times, like opening and closing a modal
  3. Watch the trend: if memory climbs each cycle and never drops back, that action is leaking

Turning an intermittent leak into a repeatable pattern is what makes it findable.

How to find memory leaks in Node.js

Node.js memory leaks show up as steadily rising memory in a long-running process, since a server stays alive far longer than a browser tab. The heap grows across requests until the process hits its limit and crashes.

To track one down:

  1. Launch with inspection: start the process with node --inspect and connect Chrome DevTools
  2. Profile memory usage: use the same heap snapshot and allocation timeline workflow against the running server for javascript memory profiling in a live process
  3. Log heap usage in production: record process.memoryUsage().heapUsed at intervals to watch the trend
  4. Confirm the signal: a heap that never returns to baseline after load is your cue to profile deeper

How to fix memory leaks in JavaScript

Fixing a leak means cutting the reference that keeps the memory reachable. Once you’ve identified the retainer in a snapshot, work through these fixes based on what’s holding it:

  1. Remove listeners on teardown: call removeEventListener when a component unmounts
  2. Clear timers: store every setInterval and setTimeout ID and clear it when done
  3. Drop DOM references: null out cached element references after removing them from the page
  4. Scope closures tightly: capture only what the closure needs, not entire objects
  5. Avoid accidental globals: always declare variables and enable strict mode

Verify the fix by repeating the leaking action in the allocation timeline. If memory now returns to baseline, the reference is released.

How to prevent memory leaks in JavaScript

Good JavaScript memory management comes down to habits, not tools. Prevention costs less than hunting leaks after they ship, and a few habits stop most leaks before they reach a heap snapshot:

  1. Reach for WeakMap and WeakSet: these hold references that don’t block garbage collection, ideal for metadata keyed to objects
  2. Build cleanup into the lifecycle: tie every listener, timer, and subscription to the teardown that removes it, so nothing outlives its component
  3. Enable strict mode: it turns accidental globals into errors instead of silent leaks
  4. Audit long-lived closures: check what timers and handlers capture before shipping them
  5. Watch memory during review: run a quick allocation timeline on new features that mount and unmount

Making cleanup part of how components are built, rather than a fix applied later, is what memory management looks like in practice.

How Bugfender helps track memory issues

Local profiling stops working when a leak only surfaces on a specific user’s device after hours of use. At that point we need visibility into what actually happened in the real session, not a reproduction attempt.

Bugfender captures logs, crashes, and user actions directly from real devices, even when nothing crashes, so we can see the sequence of events leading up to a slowdown without reproducing it locally. It works across web, iOS, and Android.

Create your free Bugfender account and start capturing production logs in minutes.

Frequently Asked Questions

What leaks most often in single-page apps?

Event listeners are the top offender in single-page apps. Components mount and unmount on every route change, and any listener added on mount but not removed on unmount pins its memory. The leak compounds because the same components cycle repeatedly, stacking a new orphaned listener with each visit.

How does garbage collection relate to leaks?

Garbage collection frees memory your code can no longer reach. A leak happens when memory stays reachable by accident, so the collector leaves it alone. Garbage collection can’t fix a leak, because from its perspective the memory is still in use. Removing the stray reference is the only fix.

How do you spot a detached DOM node?

Take a heap snapshot in Chrome DevTools and type “Detached” into the class filter. Detached nodes show up as elements no longer in the document but still held in memory. The retainers view then names the variable or closure keeping each node alive, pointing you to the reference to release.

How long does debugging a memory leak take?

It varies with how reproducible the leak is. A leak you can trigger reliably with a repeated action, like opening a modal, can be isolated in an allocation timeline within minutes. Production-only leaks that depend on specific devices or long sessions take longer, since the hard part is capturing the conditions.

Expect The Unexpected!

Debug Faster With Bugfender

Start for Free
blog author

Aleix Ventayol

Aleix Ventayol is CEO and co-founder of Bugfender, with 20 years' experience building apps and solutions for clients like AVG, Qustodio, Primavera Sound and Levi's. As a former CTO and full-stack developer, Aleix is passionate about building tools that solve the real problems of app development and help teams build better software.

Join thousands of developers
and start fixing bugs faster than ever.