Memory leaks explained
What a memory leak actually is, why they happen even in garbage-collected languages, and how to find and prevent them, with real before-and-after code.
What is a memory leak?
A memory leak is memory a program has allocated but can no longer use and never releases. The memory stays reserved for the life of the process, so usage climbs over time until the program slows, gets killed by the OS, or crashes with an out-of-memory error. Leaks are especially dangerous in long-running processes, servers, daemons, mobile apps, where a slow drip eventually exhausts everything.
Two worlds: manual vs garbage-collected
In unmanaged languages (C, C++) you allocate and free memory yourself. In garbage-collected languages (Java, C#, JavaScript, Python, Go) the runtime frees memory that is no longer reachable, but it can only collect what nothing references. So the two have different causes and the same symptom:
| World | A leak is... | The fix |
|---|---|---|
| Manual (C/C++) | You new/malloc and never delete/free, often on an error path | RAII / smart pointers, free on scope exit |
| Garbage-collected | An unintended reference keeps a dead object reachable, so the GC keeps it forever | Bound caches, unregister listeners, weak references |
“Garbage collected” does not mean “leak-proof.”
Common causes
- Unfreed allocations (manual languages), early returns and exceptions that skip the free.
- Growing collections: a cache, list, or map you keep adding to but never evict from. The single most common leak in managed languages.
- Forgotten event listeners / callbacks: registering a handler and never removing it keeps the listener, and everything it closes over, alive. Common in UI and browser code.
- Closures capturing too much: a closure that captures a large object keeps it referenced for as long as the closure lives.
- Static / global references: anything hung off a global or static field never becomes unreachable on its own.
- Unclosed resources: file handles, sockets, and DB connections leak their backing memory (and OS handles) if never closed.
A leak and its fix, in code
The most common managed-language leak is a reference you forgot to release, here, an event listener:
// LEAK: a listener that is never removed keeps everything it closes over alive
const cache = [];
function onClick() {
cache.push(new Array(1_000_000)); // grows on every click, never freed
}
button.addEventListener("click", onClick);
// the page lives for hours; cache and the handler are never collected
// FIX: bound what you keep, and remove listeners you no longer need
button.addEventListener("click", onClick, { once: true }); // auto-removes after one call
// or call button.removeEventListener("click", onClick) when you are done And the classic manual leak, an allocation skipped by an early return, solved by tying lifetime to scope:
// LEAK: allocated, but an early return skips the delete
void process(bool fail) {
int* buf = new int[1024];
if (fail) return; // leaks buf
delete[] buf;
}
// FIX: RAII / a smart pointer frees automatically on every exit path
#include <memory>
void process(bool fail) {
auto buf = std::make_unique<int[]>(1024);
if (fail) return; // buf is freed here, automatically
} // and here That second fix is RAII with a smart pointer, the single most effective leak prevention in C++.
How to detect them
- Watch the trend: a leak shows as memory that rises and never comes back down across a steady workload. Graph process memory over time first, confirm there is actually a leak.
- Heap snapshots / profilers: capture two snapshots and diff them, objects that keep growing between them are your suspects (browser DevTools, Java VisualVM/heap dumps, .NET memory profilers, Go pprof).
- Leak detectors for native code: Valgrind, AddressSanitizer (ASan), and similar tools flag allocations that were never freed.
How to prevent them
- Tie lifetime to scope: RAII / smart pointers in C++,
using/try-with-resourcesin C#/Java, context managers in Python, release on scope exit, even on errors. - Always unregister what you register, pair every listener add with a remove.
- Bound your caches: set a max size or TTL and evict; use weak references for caches that should not keep objects alive on their own.
- Be deliberate with global/static state: it lives forever by definition.
FAQ
Can a garbage-collected language leak memory?
Yes. The garbage collector only frees objects nothing references, so any lingering reference, a growing cache, a forgotten listener, a static field, keeps memory alive indefinitely. Garbage collected does not mean leak-proof.
What is the most common memory leak?
An unbounded collection: a cache, list, or map you keep adding to but never evict from. It looks innocent and grows quietly under load. It is by far the most common leak in managed languages.
How do I confirm it is a leak and not normal usage?
Run a steady, repeating workload and graph process memory over time. Normal usage plateaus; a leak keeps climbing and never returns to baseline after the work finishes.
What is the difference between a manual leak and a GC leak?
A manual leak (C/C++) is memory you allocated and never freed, often on an error path that skips the cleanup. A GC leak is an unintended reference: the object is unused but still reachable, so the collector cannot reclaim it. Different cause, same symptom, memory that only grows.
Do memory leaks always crash the program?
Not immediately. A small leak in a short-lived program may never matter. The danger is long-running processes, servers, daemons, mobile apps, where a slow drip eventually exhausts memory and the program degrades, gets killed by the OS, or crashes out of memory.
What is a weak reference and when do I use one?
A weak reference points at an object without keeping it alive, the collector may still reclaim it. Use weak references for caches and lookup tables that should not, by themselves, prevent their entries from being freed.
What tools detect memory leaks?
For managed code: heap snapshots and profilers (browser DevTools, Java VisualVM/heap dumps, .NET memory profilers, Go pprof), diff two snapshots and watch what grows. For native code: Valgrind and AddressSanitizer flag allocations that were never freed.
Related concepts
Smart pointers · Multithreading & concurrency · OOP concepts · C++ type casting · all references.
“Memory leaks” was one of kbcafe.com’s enduring developer references, linked
from forums and course pages well past its original C++ era. This is its modern restoration, covering both
the manual and garbage-collected worlds, because the leak moved but never went away.