Understand how the garbage collector works, use weak references to avoid leaks, and smooth out expensive work with debounce and throttle.
Open this lesson in KodokonV8's garbage collector is based on reachability: an object becomes collectable as soon as no chain of references links it to the roots (the stack, active scopes, globals). The GC is generational: young objects live in the *nursery*, collected often and quickly (scavenge); survivors are promoted to the old generation, handled by an incremental mark-and-sweep. Classic leaks do not come from the GC but from references you forgot: Map-based caches, listeners that are never detached, closures capturing large objects.
const strongCache = new Map();
const weakCache = new WeakMap();
let session = { user: "ada" };
strongCache.set(session, "data");
weakCache.set(session, "data");
session = null;
// Map: the key retains the object -> memory leak
// WeakMap: the entry becomes collectableA WeakMap does not retain its keys: when a key becomes unreachable from anywhere else, the whole entry disappears. Design consequences: keys must be objects (or unregistered symbols), and the structure is neither iterable nor measurable - exposing its size would reveal the behavior of the GC, which is non-deterministic. WeakRef goes further: it gives you a direct weak reference, to be dereferenced with deref().
let config = { theme: "dark" };
const ref = new WeakRef(config);
function readTheme() {
const target = ref.deref();
return target ? target.theme : "default";
}
console.log(readTheme()); // "dark"
config = null;
// After a GC pass, deref() may
// return undefined: plan a fallback.On the perceived-performance side, high-frequency events (scroll, resize, input, mousemove) can trigger hundreds of calls per second. Two complementary strategies: debounce only runs the function after a quiet period (ideal for search-as-you-type), while throttle guarantees at most one execution per interval (ideal for tracking scrolling).
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(
() => fn.apply(this, args),
delay
);
};
}function throttle(fn, interval) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= interval) {
last = now;
fn.apply(this, args);
}
};
}ref.deref() return on a WeakRef?undefined if it has been collectednull if the object has been collected