Memory leaks in browser applications compound silently over a user session. A useEffect that registers a resize listener without a cleanup function adds a listener on every re-render; after 50 renders, 50 listeners are firing simultaneously. ISO 25010:2011 resource-utilisation frames the waste; the user-visible outcome is an application that slows, stutters, or crashes after extended use — worst for users who keep tabs open for hours, such as internal tooling users or power users of dashboards.
High because undetected memory leaks degrade performance progressively throughout a user session, causing crashes or forced page reloads that interrupt workflows in ways that are difficult to trace without explicit profiling.
Profile heap usage in Chrome DevTools (Memory tab → Take heap snapshot before and after navigating key flows). For every useEffect that subscribes to an event or starts an interval, return a cleanup function.
// components/realtime-widget.tsx
useEffect(() => {
const handler = (e: MessageEvent) => handleUpdate(e.data)
socket.addEventListener('message', handler)
return () => socket.removeEventListener('message', handler) // cleanup required
}, [])
ID: performance-deep-dive.runtime-memory.memory-leak-detection
Severity: high
What to look for: Count all useEffect hooks and event listener registrations. Enumerate which have cleanup return functions vs. which do not. Look for memory profiling tools configured (Chrome DevTools, Node.js heap snapshots, third-party profilers). Check for documented results of heap snapshot analysis. Look for detached DOM nodes, unbounded event listeners, or circular references that prevent garbage collection. Before evaluating, extract and quote the first 3 useEffect hooks found that lack cleanup return statements.
Pass criteria: Memory profiling has been run on the production build. Heap snapshots show no detached DOM nodes, no unbounded listener arrays, and no circular references that prevent garbage collection. Memory remains stable during typical user workflows. At least 95% of useEffect hooks with subscriptions or listeners must include cleanup functions.
Fail criteria: No memory profiling has been done, or heap snapshots reveal detached nodes, unbounded listeners, or other signs of memory leaks.
Skip (N/A) when: Never — memory leaks affect all applications and should be checked.
Cross-reference: For DOM node count optimization, see the dom-node-count-limit check.
Detail on fail: "No memory profiling performed — risk of memory leaks undetected" or "Heap snapshot shows 4000+ detached DOM nodes after navigating between pages. Event listeners not cleaned up on route change"
Remediation: Profile your application's memory usage using Chrome DevTools Memory tab or Node.js profilers. Look for retained objects and ensure cleanup in useEffect and event listeners.
// components/live-feed.tsx — useEffect with proper cleanup
useEffect(() => { const handler = (e: Event) => {}; window.addEventListener('resize', handler); return () => window.removeEventListener('resize', handler) }, [])