Memory leak là khi bộ nhớ đáng lẽ được giải phóng nhưng vẫn bị giữ reference từ root (window/global), nên garbage collector (mark-and-sweep) coi là còn dùng và không dọn. Heap phình dần theo thời gian, app chậm rồi crash.
Các nguồn leak hay gặp:
- Timer không clear: setInterval/setTimeout không gọi clearInterval khi không cần. Callback giữ nguyên closure và mọi biến nó tham chiếu.
- Event listener không gỡ: addEventListener trên window/document mà quên removeEventListener.
- Detached DOM: node đã xoá khỏi cây DOM nhưng còn biến JS trỏ tới, nên cả subtree không được GC.
- Cache/collection vô hạn: push vào array hay Map global mà không bao giờ xoá. Dùng WeakMap/WeakRef để key không bị giữ sống.
- Biến global vô tình: quên let/const hoặc gán window.x khiến biến gắn vào global mãi.
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id); // cleanup khi unmount
}, []);Phát hiện: Chrome DevTools → Memory → Heap snapshot, chụp 2 lần rồi so sánh xem object nào tăng; hoặc Performance panel bật Memory xem đường heap leo dốc đều thay vì răng cưa. Node: process.memoryUsage() log định kỳ và heap dump qua --inspect.
Phòng tránh: cleanup trong useEffect return, AbortController cho fetch/listener, WeakMap/WeakRef cho cache, null hoá reference khi xong việc.