JavaScript · Advanced

Memory Management Basics

JavaScript cleans up memory automatically — but only for things you are truly finished with. Small leaks add up in long-running apps.

Advanced 5 min Lesson 45 of 49 Runnable examples
Before you start:ClosuresMap and Set
0/49 in JavaScript
On this page · 5 min · Advanced

Learning objectives#

  • Explain garbage collection and reachability
  • Recognise common memory leaks
  • Remove listeners and timers
  • Bound caches so they cannot grow forever

Explanation#

The garbage collector frees objects that are no longer reachable — nothing in your program can get to them any more.

A leak happens when something you no longer need is still reachable: a forgotten event listener, a running interval, or a cache that only ever grows.

Clean up: removeEventListener (or an AbortController signal), clearInterval/clearTimeout, and limit cache sizes.

In single-page apps (React, Angular), always clean up in unmount/destroy hooks.

Code example: Cleaning up and bounding a cache#

JavaScriptCleaning up and bounding a cache Runnable
// 1. Listeners you can remove in one go
const controller = new AbortController();
window.addEventListener('resize', () => {}, { signal: controller.signal });
controller.abort();                       // removes the listener


// 2. Timers you stop when done
const timer = setInterval(() => {}, 1000);
clearInterval(timer);


// 3. A bounded cache (least-recently-used)
class LruCache {
  constructor(limit) { this.limit = limit; this.map = new Map(); }
  get(key) {
    if (!this.map.has(key)) return undefined;
    const value = this.map.get(key);
    this.map.delete(key); this.map.set(key, value);   // mark as recently used
    return value;
  }
  set(key, value) {
    this.map.delete(key);
    this.map.set(key, value);
    if (this.map.size > this.limit) this.map.delete(this.map.keys().next().value);
  }
}
const cache = new LruCache(2);
cache.set('a', 1); cache.set('b', 2); cache.get('a'); cache.set('c', 3);
console.log([...cache.map.keys()]);
console.log('listener removed:', controller.signal.aborted);
Expected output
[ 'a', 'c' ]
listener removed: true
Opens this example in the full playground

Step-by-step explanation#

  1. Passing an AbortSignal lets you remove the listener later with abort().
  2. clearInterval stops the timer so its callback (and anything it references) can be freed.
  3. The LRU cache keeps at most two entries.
  4. Reading "a" made it recent, so adding "c" evicted "b".

Real-world use case#

Dashboards left open all day, chat apps and SPAs must clean up listeners, sockets and timers or the tab slowly gets heavier and slower.

Common mistakes#

Adding a listener on every render

Each render adds another listener that is never removed. Add once, and remove on cleanup.

Unlimited caches

A Map that stores every API response forever grows without limit. Cap it or use WeakMap for object keys.

Best practices#

  • Pair every addEventListener with a removal path
  • Clear timers and intervals you no longer need
  • Cap caches (LRU) or use WeakMap
  • Use DevTools Memory → heap snapshots to find leaks

Remember this#

Reachable = kept. Unreachable = cleaned up.

Every “start” (listener, timer, subscription) needs a “stop”.

Caches need a size limit.

Practice exercise#

Your task: Write startClock() that logs the time every second and returns a stop() function that clears the interval.

Show solution
JavaScript Runnable
function startClock() {
  const id = setInterval(() => console.log(new Date().toLocaleTimeString()), 1000);
  return () => clearInterval(id);
}
const stop = startClock();
setTimeout(stop, 3500); // logs 3 times, then stops
Opens this example in the full playground

Mini quiz#

Memory Management Basics quizMultiple choice Question 1 of 1

Which is a common source of memory leaks?

Score so far: 0/0

Want more? Practise all JavaScript MCQs · JavaScript interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.