Skip to main content

The Janitor, The Librarian, and The Rustacean: How Languages Manage Memory

·1099 words·6 mins

I’ve been writing JavaScript for years.

I’ve created objects, arrays, functions, closures, promises, maps, sets, and enough React components to make Chrome cry.

Yet if you had asked me a few years ago:

“What happens to all that memory once you’re done using it?”

My answer would’ve been something along the lines of:

“I don’t know. The browser figures it out.”

And honestly, that’s not entirely wrong.

The browser does figure it out.

But that realization led me down a rabbit hole of understanding Garbage Collection, why languages like JavaScript and Java have it, why languages like C++ and Rust don’t, and why memory management is probably one of the biggest philosophical differences between programming languages.

Let’s talk about it.


The Hotel Room Problem
#

Imagine you’re staying in a hotel.

Every time you need a room, the hotel gives you one.

Done with it?

Great.

Now someone needs to clean it.

There are essentially three ways to solve this problem.

Option 1: Clean It Yourself
#

This is the C/C++ approach.

You check into a room.

When you’re done, you explicitly tell the hotel:

“I’m leaving. Please clean this room.”

If you forget?

The room remains occupied forever.

If you accidentally tell them to clean the room twice?

Chaos.

This is effectively what malloc() and free() are doing in C.

int* number = new int(42);

delete number;

You allocated memory.

You freed memory.

Everything is your responsibility.

Maximum control.

Maximum power.

Maximum opportunity to shoot yourself in the foot.


Option 2: Hire A Janitor
#

This is JavaScript.

You don’t clean anything.

You simply stop using the room.

At some point, a janitor walks around the hotel and says:

“Nobody seems to be using this room anymore.”

And cleans it.

That’s Garbage Collection.

The language runtime periodically identifies memory that is no longer reachable and reclaims it automatically.

The developer never explicitly frees memory.


Option 3: Hire An Extremely Strict Librarian
#

This is Rust.

Instead of a janitor cleaning things later, Rust introduces a librarian who keeps track of exactly who owns every book.

The rules are simple:

  • Every piece of data has one owner.
  • When the owner goes away, the data is cleaned up.
  • Ownership can be transferred.
  • Multiple readers are allowed.
  • Only one writer is allowed.

If you violate any of these rules:

The code doesn’t compile.

Not “throws an exception.”

Not “fails in production.”

It simply refuses to build.


So What Exactly Is Garbage Collection?
#

Let’s start with a simple example.

let user = {
  name: "Walter White"
};

JavaScript allocates memory for that object.

Now imagine:

user = null;

Nobody references the object anymore.

The object still physically exists in memory for a short period.

But eventually the Garbage Collector notices:

“Nobody can reach this object anymore.”

And removes it.

That’s the key concept:

Reachability
#

Modern JavaScript Garbage Collectors care about whether an object is reachable from the application’s roots.

Roots typically include:

  • Global variables
  • Active function calls
  • Local variables currently on the stack

If an object can no longer be reached from any root, it becomes garbage.


The Mark And Sweep Algorithm
#

Most modern JavaScript engines use some variation of Mark and Sweep.

The algorithm is surprisingly simple.

Imagine the Garbage Collector as Thanos.

Not the snapping part.

The part where he scans the universe.

Step 1: Mark
#

Start from all root objects.

Mark everything reachable.

window.user
   -> address
      -> city

Everything connected gets marked as alive.


Step 2: Sweep
#

Anything that wasn’t marked gets deleted.

Gone.

Reduced to atoms.

The collector walks through memory and frees everything unreachable.

Which is why the algorithm is called:

Mark and Sweep

Simple name.

Very expensive job.


Why Not Just Count References?
#

You might think:

“Why don’t we simply count how many references point to an object?”

Many older systems tried exactly that.

let a = {};
let b = a;

Reference count = 2.

Remove one reference.

Count becomes 1.

Remove the second.

Count becomes 0.

Delete object.

Easy.

Except for one problem.


The Spider-Man Problem
#

Imagine Peter Parker and MJ storing each other’s phone numbers.

let peter = {};
let mj = {};

peter.friend = mj;
mj.friend = peter;

Now remove the external references.

peter = null;
mj = null;

The two objects still point to each other.

A simple reference counter sees:

“Each object still has one reference.”

So neither gets deleted.

Memory leak.

Mark-and-Sweep solves this beautifully.

The collector asks:

“Can I reach either of these from the roots?”

No.

Delete both.

Problem solved.


The Hidden Cost Of Garbage Collection
#

At this point GC sounds magical.

And honestly, it is.

But magic has a price.

Someone still has to stop and inspect memory.

Someone still has to traverse object graphs.

Someone still has to clean things up.

This introduces runtime overhead.

That’s why GC-heavy applications sometimes experience pauses.

Modern engines like V8 have become incredibly sophisticated with generational, incremental, concurrent and parallel collection strategies, but the core idea remains the same.

The janitor still needs to work.

He’s just gotten much faster.


Enter Rust
#

Now let’s switch universes.

Imagine if instead of hiring a janitor, we simply never allowed hotel rooms to become ambiguous in the first place.

That’s Rust.

Rust does not use a Garbage Collector.

Instead, it uses a system called Ownership.

{
    let name = String::from("Jesse");
}

The moment the scope ends:

}

The memory is automatically released.

No Garbage Collector.

No runtime scan.

No janitor.

Ownership rules determine exactly when cleanup should happen.


So Which One Is Better?
#

Neither.

They’re solving different problems.

Garbage Collected Languages
#

Examples:

  • JavaScript
  • Java
  • C#
  • Go (with GC)

Pros:

  • Easier developer experience
  • Faster iteration
  • Fewer memory management mistakes

Cons:

  • Runtime overhead
  • Less predictable performance
  • Potential GC pauses

Ownership / Manual Memory Languages
#

Examples:

  • Rust
  • C++
  • C

Pros:

  • Greater control
  • Predictable performance
  • Lower runtime overhead

Cons:

  • Steeper learning curve
  • More responsibility
  • Easier to introduce bugs (except Rust, which shifts the pain to compile time)

Final Thoughts
#

One of the biggest realizations for me was that memory management isn’t just an implementation detail.

It’s a language philosophy.

JavaScript says:

“Trust the runtime.”

C++ says:

“Trust the developer.”

Rust says:

“Trust the compiler.”

And that single decision ends up influencing everything from developer experience to performance characteristics.

The next time you create an object in JavaScript, remember:

Somewhere deep inside V8, a tiny janitor is waiting patiently for you to stop using it.

And somewhere in the Rust ecosystem, a very angry librarian is making sure nobody loses track of a book.