Can you have a memory leak in a garbage-collected language?
Yes, and this surprises people who assume garbage collection makes leaks impossible. It removes one class of leak and leaves another entirely intact.
What garbage collection actually does. It reclaims memory that is unreachable — objects no code can still get to. It does not reclaim memory that is reachable but useless. If your program holds a reference to something it will never use again, the collector correctly concludes it is still needed and keeps it forever.
So the definition shifts: in a manual-memory language a leak is memory you forgot to free; in a garbage-collected language it is a reference you forgot to drop.
The common causes:
Collections that only grow. A cache, list, map or set that accumulates entries and never evicts. This is by far the most frequent cause in practice. A cache without a bound or a TTL is a leak with a friendly name.
Event listeners and subscriptions not removed. Registering a handler creates a reference from the emitter to your object. If the object is discarded but the listener is never unregistered, the emitter keeps it alive — and this is the classic React useEffect cleanup bug and the classic DOM leak.
Closures capturing more than intended. A closure holds references to everything in its enclosing scope, which can retain far larger structures than the small value you meant to keep.
Static and global references, which live for the process lifetime by definition.
Timers and intervals never cleared, each holding its callback and its captures.
Detached DOM nodes removed from the document but still referenced in JavaScript.
How to find them: take heap snapshots at two points and compare retained size, then examine the retaining path — the chain of references keeping an object alive. Chrome DevTools, Node's inspector and JVM profilers all support this.
Weak references exist for exactly this problem, letting you reference without preventing collection.