In garbage-collected languages, a memory leak isn't unfreed allocations — it's allocations the GC can still reach but the program will never use. The classic pattern: a long-lived map keyed by request id, or an event listener registered on window that captures a now-unmounted component, or a global cache with no eviction policy.
Manual-memory languages (C, C++, older Rust without RAII) leak when free / delete is forgotten on a path. Modern Rust and C++ make leaks rare with smart pointers, but the listener-and-cache pattern still applies in any language with closures and a heap.
Diagnosis is mechanical: capture a heap snapshot with the runtime's profiler, compare two snapshots taken minutes apart under load, and look at what grew. The retainer chain leads back to the root cause faster than reading code can.
Review heuristic
Every long-lived collection (cache, registry, event bus, observer list) needs an eviction or unsubscribe path that fires deterministically. "It'll get GC'd" is true for the value but not for the reference holding it.
External reference
CWE-401: Missing Release of Memory after Effective Lifetime — the canonical industry classification for this bug class. Useful when filing tickets, writing security policies, or arguing with a static analyzer.