Suggest an editImprove this articleRefine the answer for “Timers and event listeners”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)`setTimeout`, `setInterval` and `addEventListener` create asynchronous tasks that **hold references** to everything used inside their callbacks (via closures). **Key point:** while a timer is active or a listener is still attached, every object its callback touches is considered reachable, so the GC cannot remove it.Shown above the full answer for quick recall.Answer (EN)Image## 1. What timers and event listeners do - `setTimeout`, `setInterval`, `addEventListener` create **asynchronous tasks** that **live separately** from the main JS execution stack. - These tasks **hold references** to everything used inside their callbacks (via closures). While a timer is active or a listener is still "subscribed", every object its callback touches **is considered reachable** → the GC cannot remove it. ## 2. Why cleanup matters ### Without cleanup, objects stay in memory forever ```javascript function startTimer() { const bigData = new Array(1e6).fill('data'); setInterval(() => console.log(bigData.length), 1000); } startTimer(); ``` Here `bigData` is never freed: - the interval lives forever; - the closure holds a reference to `bigData`; - the GC considers `bigData` "alive" → memory grows. You need to clean up: ```javascript const id = setInterval(...); clearInterval(id); ``` ### Event listeners are the same story ```javascript const button = document.getElementById('btn'); button.addEventListener('click', () => console.log('clicked')); button.remove(); // the element is removed, the listener remains ``` Now: - the `button` element is removed from the DOM, but the callback is still kept in memory (JS still has a reference to it); - the GC cannot remove the object because a registered handler is "looking" at it. Solution: ```javascript button.removeEventListener('click', handler); ``` ## 3. Why this is especially critical in SPAs > In an SPA the page does not reload. > Anything that isn't cleaned up lives until the user closes the tab. For example: - you navigate between pages; - components mount and unmount; - if timers and listeners are not removed, each "old" component keeps existing in memory → **garbage builds up**. After 30 minutes of using a React app you might see: - hundreds of active intervals, - dozens of "zombie" listeners, - heap memory growing without being reclaimed. ## 4. How exactly this holds memory (the mechanism) | Object | Reference | Why it isn't cleaned up | |---|---|---| | Timer | the callback (closure) | holds references to all closure variables | | Listener | callback + DOM element | live until `removeEventListener` is called | | WebSocket, RxJS, setInterval | an active subscription | holds the whole context | | React useEffect | without cleanup | effects are never canceled, references persist | The GC considers all of this **reachable**, because there are active callback references. ## 5. How to clean up correctly ### Timers ```javascript useEffect(() => { const id = setInterval(() => {...}, 1000); return () => clearInterval(id); }, []); ``` ### Event listeners ```javascript useEffect(() => { const handler = () => console.log('scroll'); window.addEventListener('scroll', handler); return () => window.removeEventListener('scroll', handler); }, []); ``` ### Subscriptions (RxJS, WebSocket, custom events) ```javascript useEffect(() => { const sub = stream$.subscribe(...); return () => sub.unsubscribe(); }, []); ``` ## 6. What happens if you don't clean up | Consequence | What happens | |---|---| | Memory growth | callback closures hold large objects | | UI lag | the event loop is clogged with active callbacks | | Duplicate calls | old listeners fire alongside new ones | | CPU growth | too many active intervals | | "Out of memory" | the browser or tab crashes | | "Zombie components" | old React components keep living and running effects | ## 7. Tools for detecting this - **Chrome DevTools → Memory → Heap snapshot** → look for "Detached DOM nodes" and `Timeout`, `EventListener` objects. - **Performance → Record** → "Long tasks". - **React DevTools Profiler** → check that components unmount. - **Sentry Performance** → heap growth, leaks, FPS degradation. ## 8. Summary | What to remember | Why | |---|---| | `setTimeout` / `setInterval` must be cleared | otherwise the callback stays alive | | `addEventListener` must be removed | otherwise the DOM node and function are not freed | | In React, always `return () => cleanup()` in `useEffect` | prevents "zombie" effects | | SPAs have no memory "restart" | everything accumulates until you clean it up manually | | Memory leaks = lag + crashes | the GC cannot remove "alive" references | **The main idea:** > A timer or a listener is like an open tab in memory. > If you don't close it, you end up with hundreds of them, and the browser "dies".For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.