How memory leaks happen and how the Mark-and-Sweep algorithm works.
Timothy was staring at the dashboard he was building. It was getting slow. Sluggish.
"I don't understand," he said. "I am just creating objects. I'm not running heavy calculations. Why is the browser freezing?"
Margaret walked to the chalkboard and drew a large rectangle.
"Because you are running out of space, Timothy. This is the Heap."
The Heap (Memory is Finite)
"The Heap is where JavaScript stores objects, arrays, and functions," Margaret explained. "It is large, but it is not infinite. If you keep allocating memory without freeing it, you exhaust the available space. The browser freezes."
"But I don't free anything," Timothy said. "I just move to the next function."
"You don't have to free memory manually," Margaret corrected. "The Garbage Collector (GC) does it for you. But it follows very strict rules."
Reachability (Mark-and-Sweep)
Margaret drew a small circle at the top of the board and labeled it Root (Window).
"How does the GC know which objects to keep and which to destroy?" she asked.
"It checks if I'm using them?"
"No," Margaret said. "It checks if they are Reachable."
She drew arrows connecting the Root to various objects.
"This is the Mark-and-Sweep algorithm," she said.
-
The Roots: The GC starts at the "Roots" (The Global
windowobject and the current Call Stack). - The References: It follows every reference (every variable, every property). If an object is connected to a Root, it is marked "Safe."
- The Sweep: Any object that cannot be reached from a Root is considered garbage. It is marked for deletion and removed during the next GC cycle.
The Disconnect
Margaret erased a line on the board.
let user = { name: "Timothy" };
// 1. The 'user' variable is a Reference from the Root to the object.
user = null;
// 2. The Reference is broken.
"When you set user = null," Margaret explained, "you didn't delete the object. You just broke the Reference."
"So the object is stranded?" Timothy asked.
"Exactly. The next time the GC runs, it will start at the Root, try to find that object, and fail. Since it is unreachable, the space is reclaimed."
The Memory Leak
Timothy looked at his slow code. "So if my app is slow, it means I am holding onto objects I don't need?"
"Yes," Margaret said. "You have a Memory Leak. You are accidentally keeping a Reference alive."
She wrote a common trap on the board:
function startDashboard() {
const hugeData = new Array(100000).fill("Data");
// The Trap:
window.addEventListener('resize', () => {
console.log(hugeData.length);
});
}
"Trace the References," Margaret commanded.
Timothy looked at the diagram.
-
The Root (Window) has an Event Listener attached to it (
resize). -
The Listener is a function that uses
hugeData. - Because of Closure, the Listener must hold a reference to
hugeData.
"I see it," Timothy realized. "The Window holds the Listener. The Listener holds the Data."
"Correct," Margaret said. "Even if startDashboard finishes running, that connection remains. The GC sees a clear path from the Root to your massive array. It cannot delete it."
The Solution
"How do I fix it?" Timothy asked.
"You must break the reference," Margaret said.
She added the cleanup code:
window.removeEventListener('resize', myListener);
"Remove the listener," she said. "The connection breaks. The huge data becomes unreachable. The Garbage Collector sweeps it away, and your browser breathes again."
The Conclusion
"Memory management isn't about micromanaging every object," Margaret concluded, wiping the chalk dust from her hands. "It's about understanding the invisible connections that keep data alive. Break the connections you don't need, and let the garbage collector do its job."
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.
Top comments (20)
Hi, thanks for this post.
I really like your explaining interesting concepts in stories.
in a world where the RAMs are more abundant and cheaper, and since web devs work on laptops/servers not chips, a typical JS developer may easily be inclined to not-think about memory management -true, it is not as C++ development in the end- but i suppose it can affect web performance for certain cases.
I suppose event handlers are the easiest trap, as you defined. In react, as they are mostly defined in useEffects, it must be cared and removed during the unmounting of the component, which is something that can be forgotten in haste (speaking from experience)
β€π―π
We loved your post so we shared it on social.
Keep up the great work!
thank you, Sloan!
it's a privilege to be part of the dev.to community.
πβ¨β€
Nice work
β¨πΉπ
The way you frame GC as βtracing invisible connectionsβ instead of deleting objects directly makes the whole Mark-and-Sweep idea click instantly. The event listener + closure example is especially good β thatβs exactly the kind of leak many developers hit without realizing why memory keeps growing.
I also like that the takeaway isnβt βGC is magicβ but βGC is predictable if you understand reachability.β That mindset shift alone can save hours of debugging sluggish apps.
Great write-up. Curious if youβre planning to cover other real-world leak patterns next (intervals, detached DOM nodes, caches, etc.) β this series is super easy to follow and very practical.
β¨π―π
Please, correct me if I'm wrong, but I dare to say that functional programming helps to avoid these traps. The function considered wouldn't have been implemented in pure functional programming.
Also, I'm a Svelte user, and the framework/compiler itself helps to avoid these circumstances.
In the end, if we follow a good coding structure (which I believe functional programming brings) and rely on the great tools we have, we can code with a light heart, without overthinking issues like GC, but understanding it is a must; therefore, thanks for the article.
π―β¨β€οΈ
A very clear and concise explanation of a complex topic. Garbage collection is one of those features we often take for granted until things go wrong. Thanks for sharing Aaron!
πβ¨πΉ
What I like about this is that it doesnβt treat the GC as some mystical background daemon β it treats it like a graph problem. Once you see memory as βwho can still reach what,β a lot of JS weirdness suddenly stops being weird.
The part that really clicked for me is the event listener example. Thatβs where most leaks actually live in real apps: not in massive computations, but in tiny, invisible references that never get severed. Closures + long-lived roots is the silent killer.
I also appreciate the emphasis on breaking references instead of βfreeing memory.β That distinction matters. Youβre not managing memory directly, youβre managing connectivity. The GC just follows the map youβve drawn, even if you forgot you drew it.
This way of explaining it scales beyond JavaScript too. Once you internalize βroots β references β reachability,β you start spotting leaks in architectures, not just code. Long-lived services holding onto short-lived data is the same mistake, just at a different layer.
Really solid mental model. If more people thought about memory as relationships instead of objects, half the performance bugs Iβve seen would disappear.
Now I can clearly explain to someone what gc actually does! Thanks for the post
πͺπ―β€
So thoughtful. I appreciate your every post. Thanks again!
β€πΉπ
Really Grate Explaination
thanks riyyan! cheers π―β¨π
Some comments may only be visible to logged-in visitors. Sign in to view all comments.