Suggest an editImprove this articleRefine the answer for “How to avoid memory leaks”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**A memory leak in JavaScript is always a stray live reference: no reference, no leak.** So fighting leaks comes down to shortening object lifetimes and breaking links in time: remove event listeners with the very same callback, clear `setInterval` and `setTimeout`, cancel unfinished `fetch` calls through `AbortController`, never keep references to nodes removed from the DOM, do not capture large objects in closures without a reason, and bound global caches. For object keyed caches use `WeakMap`, for string keyed ones an LRU with a hard limit. In React that means cleanup in `useEffect`, in Node.js it means closing connections, pools and streams on shutdown. **Key point:** you find a leak by measuring, not by guessing: take a heap snapshot, look for "Detached HTML elements" and retainers, and add a test that runs the scenario N times and expects memory to stabilise.Shown above the full answer for quick recall.Answer (EN)Image**You avoid memory leaks by managing references: no reference, no leak.** Anything still reachable from the roots (global variables, closures, the DOM, caches) will never be collected, so the main practices are to shorten object lifetimes and to break links in time: handlers, timers, caches, DOM references. ## Theory ### TL;DR - The base principle is **no reference, no leak**. The GC only collects unreachable objects. - Remove event listeners with **the same callback**, clear timers, cancel `fetch` through `AbortController`. - Do not keep references to **detached** DOM nodes, and do not capture large objects in closures without a reason. - Caches: `WeakMap` / `WeakSet` for object keys, an **LRU with a limit** for string keys. - In React this is **cleanup** in `useEffect`, in Node.js it is closing connections, pools and streams plus handling `SIGTERM`. - `FinalizationRegistry` is only for "soft" cleanup, never build business logic on it. - Hunt leaks with tools: heap snapshots, allocation instrumentation, `--trace-gc`, RSS and heapUsed metrics. ### Quick example ```javascript function mount(button) { const onClick = () => console.log('clicked'); const id = setInterval(tick, 1000); const ac = new AbortController(); button.addEventListener('click', onClick); fetch('https://api.example.com/data', { signal: ac.signal }); // Return a cleanup function: everything created must be torn down return () => { button.removeEventListener('click', onClick); // the very same callback clearInterval(id); ac.abort(); }; } ``` ### Typical sources of leaks and how to close them #### Event handlers and subscriptions The problem: dangling listeners hold both the DOM node and all the data captured in its closure. ```javascript const onClick = () => {}; button.addEventListener('click', onClick); // ... button.removeEventListener('click', onClick); // must be the very same callback ``` In React, always do **cleanup** in `useEffect`: ```javascript useEffect(() => { const onScroll = () => {}; window.addEventListener('scroll', onScroll); return () => window.removeEventListener('scroll', onScroll); }, []); ``` In NestJS and when working with `EventEmitter`, drop subscriptions in `onModuleDestroy()`. #### Timers and intervals ```javascript const id = setInterval(tick, 1000); // ... clearInterval(id); ``` In Node.js 20+ you can use an **AbortSignal** for timers: ```javascript const ac = new AbortController(); setTimeout(cb, 5000, { signal: ac.signal }); // ... ac.abort(); ``` #### Unfinished fetches and streams Cancel requests through `AbortController` and close your streams. ```javascript const c = new AbortController(); const res = await fetch(url, { signal: c.signal }); // ... c.abort(); ``` #### Detached DOM nodes Do not store references to nodes removed from the DOM, and clear DOM caches after `remove()`: ```javascript elem.remove(); elemRef = null; // give the GC a chance ``` #### Large closures Do not capture **large objects** without a reason, pass the **minimum** into callbacks, and break references once you are done: ```javascript let big = getHugeObject(); doWork(() => { /* we only use a small part */ }); big = null; ``` #### Global singletons and caches Everything that lives in a module or in the global scope lives "forever". Bound its size, add eviction, or use weak references. ### Caches without leaks For object keys use `WeakMap` or `WeakSet`: they do not get in the way of garbage collection. ```javascript const memo = new WeakMap(); function compute(obj) { if (memo.has(obj)) return memo.get(obj); const val = heavy(obj); memo.set(obj, val); return val; } ``` For string or number keys weak collections do not apply, so use an **LRU** with a limit: ```javascript class LRU { constructor(limit = 500) { this.limit = limit; this.map = new Map(); } get(k) { const v = this.map.get(k); if (v) { this.map.delete(k); this.map.set(k, v); } return v; } set(k, v) { if (this.map.has(k)) this.map.delete(k); this.map.set(k, v); if (this.map.size > this.limit) { const oldest = this.map.keys().next().value; this.map.delete(oldest); // evict the oldest entry } } } ``` Separately: `FinalizationRegistry` is only good for **soft** cleanup of external caches and handles. Do not rely on the timing or the order of the call, and never build business logic on finalizers. ### React and Next.js - **Clean up** every side effect: `useEffect` should return `() => { ... }`. - Do not store huge objects in `useRef` or in context unless you need to. - Avoid endless microtask chains of `Promise`s: they delay both rendering and collection. - Unsubscribe from Observables and WebSockets in the cleanup function. - For long lists use virtualisation, so that fewer objects exist at the same time. ### Node.js and NestJS - Close connections and pools: - Prisma: `await prisma.$disconnect()` on shutdown. - Redis, MongoDB, PostgreSQL: `.quit()`, `.close()`, `.end()` in `onModuleDestroy()`. - Streams: always `.destroy()` or `.end()` on errors and timeouts. - `EventEmitter`: `removeListener` or `removeAllListeners` when unloading. - Avoid uncontrolled `setInterval`, prefer a `setTimeout` loop with checks and a way to abort. - Handle shutdown signals: ```javascript process.on('SIGTERM', async () => { await app.close(); // close the server, the database, the queues process.exit(0); }); ``` For async work and queues: set **timeouts** and **concurrency limits** (semaphores or `p-limit`, for example), keep **backpressure** on sockets and streams, meaning do not read faster than you process, and do not hold large arrays of promises in memory, process them in chunks. ### Tools for finding leaks, and guardrails #### Browser - **Memory, Heap snapshot:** look for "Detached HTML elements" and their retainers. - **Allocation instrumentation:** shows who allocates and who retains. - **Performance:** long microtasks and frequent collections are a smell. #### Node.js - `--inspect` together with Chrome DevTools, then heap snapshots. - `heapdump` or `clinic` for snapshots in production. - Collector logs: `node --trace-gc app.js`, for diagnostics, not for production. - Metrics: monitor RSS and `heapUsed`, and alert on a growth trend. #### Guardrails in code and CI - Limits on cache and queue sizes. - An end to end test: "ran the scenario N times, memory stabilised". - Linter rules: ban anonymous listeners where removal is required, and ban `setInterval` without an explicit `clearInterval`. ### Common mistakes - **Removing a listener with a different function.** `removeEventListener('click', () => {})` removes nothing: it needs the same callback reference you added. - **Forgetting `clearInterval` on unmount.** The interval outlives the component and keeps its whole closure alive. - **Keeping a reference to a node after `remove()`.** The node becomes detached but stays alive, together with its entire subtree. - **An unbounded cache in module scope.** A global `Map` with no eviction grows for as long as the process lives. - **Trying to use `WeakMap` with string keys.** Weak collections accept objects only, primitive keys need an LRU. - **Hoping `FinalizationRegistry` will replace explicit resource closing.** The callback may never run. - **Calling `global.gc()` or expecting `= null` to free memory instantly.** The runtime decides when collection happens. - **Hunting a leak by eye instead of by heap snapshots.** Without comparing two snapshots and reading retainers you are only guessing.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.