What JavaScript Chooses to Forget: Understanding Mark-and-Sweep Garbage Collection
Mark-and-sweep is a garbage collection algorithm where JavaScript marks all values reachable from root references and then removes any values that are no longer reachable from the program.
Thereās a quiet decision happening every time your code runs.
You create values.
Objects, arrays, functions.
They live in memory.
But at some point, they need to disappear.
Not because you asked them to.
But because they are no longer part of the program.
So the real question becomes:
When is something safe to forget?
Not About UsageāAbout Reachability
Itās easy to assume that JavaScript removes things you no longer use.
But thatās not quite true.
JavaScript doesnāt track usage.
It tracks reachability.
If a value can still be reached from somewhere in your program, it stays.
If it canātā
it becomes a candidate for removal.
Where Everything Begins
To understand this, you need a starting point.
JavaScript defines a set of roots:
- global variables
- the current execution context
- the call stack
From these roots, the engine follows every reference.
Like tracing connections in a graph.
If something can be reached through those connections, itās still alive.
A Value That Disappears
Consider this:
let user = {
name: "Rishi"
}At this moment, thereās a clear path:
Global ā user ā { name: "Rishi" }Now:
user = nullThat path is gone.
The object still exists in memoryā
but nothing points to it anymore.
It has become unreachable.
The Two Steps That Follow
This is where the mark-and-sweep algorithm comes in.
It works in two simple phases.
Mark
The engine starts from the roots and marks everything it can reach.
Every object, every reference, every connection.
Anything that can still be accessed gets marked.
Sweep
Then it looks at everything else.
Anything not markedā
is removed.
Not because it was unused.
But because it was no longer connected to anything that mattered.
Why This Model Works
This approach avoids a common problem.
Consider two objects that reference each other:
let a = {}
let b = {}
a.ref = b
b.ref = aThey form a loop.
At first glance, it feels like they should never be removed.
But if nothing else points to themā
they are still unreachable from the roots.
So both get collected.
The loop doesnāt matter.
Only reachability does.
When Things Stay Longer Than Expected
Problems appear when something remains reachable unintentionally.
For example:
let cache = []
function add(item) {
cache.push(item)
}Even if old items are no longer neededā
they are still referenced by cache.
So they remain in memory.
Not because the engine failed.
But because the program still holds onto them.
The Subtle Role of Closures
Closures make this even less obvious.
A function can carry variables from its creation context:
function create() {
const bigData = new Array(1000000)
return function () {
console.log("hi")
}
}
const fn = create()Even though bigData is never usedā
it remains reachable through the closure.
So it stays.
Not because itās needed.
But because itās still connected.
A Different Way to Think About Memory
Itās tempting to think of memory as something that grows and shrinks based on usage.
But JavaScript doesnāt see it that way.
It sees memory as a network.
Values are nodes.
References are connections.
And garbage collection is simply the process of removing nodes that are no longer connected to the roots.
A Final Thought
JavaScript doesnāt decide what matters.
Your program does.
Every reference you keepā
every structure you buildā
defines what stays alive.
Mark-and-sweep simply follows those decisions.
It doesnāt ask whether something is useful.
Only whether it is still reachable.
And once that connection is goneā¦
the engine quietly lets it go.