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.
On this page · 5 min · Advanced
On this page
- Learning objectives
- Explanation
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Advanced
- Updated
- Oct 27, 2026
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#
// 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);
[ 'a', 'c' ] listener removed: true
Step-by-step explanation#
- Passing an AbortSignal lets you remove the listener later with abort().
- clearInterval stops the timer so its callback (and anything it references) can be freed.
- The LRU cache keeps at most two entries.
- 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#
Each render adds another listener that is never removed. Add once, and remove on cleanup.
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
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
Mini quiz#
Which is a common source of memory leaks?
Want more? Practise all JavaScript MCQs · JavaScript interview questions
My notes#
Saved in this browser.