Skip to main content

Timers and event listeners

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)

ObjectReferenceWhy it isn't cleaned up
Timerthe callback (closure)holds references to all closure variables
Listenercallback + DOM elementlive until removeEventListener is called
WebSocket, RxJS, setIntervalan active subscriptionholds the whole context
React useEffectwithout cleanupeffects 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

ConsequenceWhat happens
Memory growthcallback closures hold large objects
UI lagthe event loop is clogged with active callbacks
Duplicate callsold listeners fire alongside new ones
CPU growthtoo 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 rememberWhy
setTimeout / setInterval must be clearedotherwise the callback stays alive
addEventListener must be removedotherwise the DOM node and function are not freed
In React, always return () => cleanup() in useEffectprevents "zombie" effects
SPAs have no memory "restart"everything accumulates until you clean it up manually
Memory leaks = lag + crashesthe 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".

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.