In this chapter
We'll learn the two ways memory gets cleaned up — by the programmer (manual memory management) or automatically (garbage collection) — explained as two kinds of restaurant, plus what pointers and references are, and why a garbage collector still can't fix every leak.
The Problem in Real Life
"JavaScript has garbage collection," Anna says. "It's supposed to clean up memory automatically. So how can it leak?"
John smiles. "Garbage collection is brilliant — but it has one rule, and if you understand the rule, you'll understand every leak you ever see in JavaScript, Java, Python or C#. Let's go to a restaurant."
The garbage collector only throws away what nobody can reach. If you keep holding on, it has to keep it.
John
Clearing Your Own Table vs. Staff Who Clear It for You
Someone has to clean up
Every piece of heap memory must eventually be given back — either by the programmer or by the language.
Manual cleanup is risky
Forget to clean up and memory leaks; clean up too early and the program reads garbage.
Automatic isn't magic
Automatic cleanup only removes data that nothing references anymore.
Garbage Collection, Manual Memory and Pointers
Heap memory has to be given back when it's no longer needed, or the program slowly fills up. There are two ways to do it.
- Manual memory management — a restaurant where you clear your own table: in languages like C and C++, the programmer asks for heap memory (in C, with a function called
malloc) and must give it back when finished (withfree). It's like a restaurant where you must carry your own plates back. It's very fast and gives full control — but if you forget, the plates pile up (a leak), and if you clear a table while someone is still eating, they reach for food that's gone (a dangling reference — chapter 5). - Garbage collection — a restaurant with staff who clear tables: in languages like JavaScript, Java, Python, C# and Go, a helper called the garbage collector (GC) runs every now and then, walks around, and clears every table that nobody is sitting at anymore. You never call
free. This removes whole classes of bugs, which is why most modern languages use it. - The garbage collector's one rule — reachability: how does the GC know a table is free? It starts from the things the program can definitely still use — global variables, and the variables on the current stack trays — and follows every reference from them, and every reference from those, and so on. Everything it can reach is in use and stays. Everything it can't reach is garbage and is thrown away. If something is still referenced from anywhere reachable, the GC must keep it, even if the program will never actually use it again.
| Feature | Manual (clear your own table) | Garbage collection (staff clear tables) |
|---|---|---|
| Languages | C, C++ | JavaScript, Java, Python, C#, Go |
| Who frees memory | The programmer (free, delete) | The garbage collector, automatically |
| Speed and control | Maximum | Very good, with small pauses |
| Common bugs | Leaks, dangling pointers, freeing twice | Leaks from references kept too long |
| Where it's used | Operating systems, games, embedded devices | Most web, business and app software |
| Feature | Pointer (C, C++) | Reference (JavaScript, Java, Python, C#) |
|---|---|---|
| What it holds | A raw memory address | A managed link to an object |
| Can do arithmetic on it? | Yes (move to the next address) | No |
| Can point somewhere invalid? | Yes | No — always to a real object (or null) |
| Tracked by a garbage collector? | No | Yes |
How the garbage collector decides
Roots
global variables + variables on the stack
Follow every reference
and every reference from those
Reachable → keep
e.g. the timers map and every timer in it
Unreachable → throw away
e.g. a hold object nobody points to
Pointers and references — two kinds of address notes: a pointer (in C and C++) is a raw memory address stored in a variable — like a written street address that you can change, add to, or point at anything, even somewhere wrong. Powerful, but easy to misuse. A reference (in JavaScript, Java, Python, C#) is a safer, managed version: it always points to a real object, you can't do arithmetic on it, and the language and garbage collector keep track of it. Both answer the question "where is the data?" — the reference just has safety rails.
The cost of garbage collection: cleaning tables takes time. Every so often, the GC needs a short moment to do its walk, which can briefly pause or slow the program. Modern collectors are very clever about keeping these pauses tiny. And when the heap is almost full, the GC works harder and harder to find space — which is exactly what the seat-hold graph showed before each crash.
Some languages use other ideas: Rust, for example, has no garbage collector but still prevents most memory bugs, using strict rules about which variable "owns" each piece of data, checked before the program runs. You don't need the details — just know that memory management is a key design choice for every language.
Back to the leak: now Anna can say exactly why JavaScript's GC isn't helping. The timers map is created when the service starts and lives for the whole day — it's always reachable. Every timer stored in it is therefore reachable too. And each timer holds on to its little function, which holds on to the seatId. The GC sees all of them as "in use." They're not garbage to the GC — they're things the program is still pointing at. 300,000 holds a day, never removed from the map.
Key Takeaway
Heap memory must be given back either manually (C/C++: you free it yourself — fast but risky) or automatically by a garbage collector (JavaScript, Java, Python, C#, Go). The garbage collector removes only what can't be reached from the program's live variables. So in a garbage-collected language, a leak means something reachable is still holding references to data you no longer need.
Why This Matters
Almost every language you'll use at work has a garbage collector, so you'll rarely free memory by hand. But you'll still meet leaks — and they'll always be the same shape: something long-lived, like a cache, a list, a map or an event listener, quietly holding references it should have let go of. Understanding reachability is how you find them.
Anna knows why the GC can't help: the map is always reachable. But one thing still puzzles her — local variables disappear when a function ends, so why does the timer's little function still remember seatId hours later? That question is about how long data lives, and where it lives inside a process.
