The idea
What it is
An object isn't permanent. It has a life — it gets created, it does its job for a while, and eventually it goes away. Understanding that journey is what stops you from leaking memory, holding files open forever, or using something that's already gone.
Think of a hotel room. When a guest checks in, the room is constructed for them (clean towels, fresh sheets — the constructor runs). While the guest is staying, the room is active and in use. When the guest checks out, nobody is pointing to that room anymore — it's unreachable. Later, housekeeping comes through, resets the room, and makes it available again — that's cleanup. The room had a clear beginning, middle, and end.
The four stages to remember
Constructed (the constructor runs once) → Active (something still references it) → Unreachable (the last reference is dropped) → Reclaimed (memory freed, destructor/finalizer runs). Every object you ever create walks this path.
Mechanics
How it works
Birth: the constructor runs once
When you write new Connection(), the runtime first allocates memory for the object, then calls its constructor. The constructor runs exactly once per object and its job is initialization — setting fields to valid starting values and acquiring any resources the object needs, like opening a file or a network socket. After it finishes, the object exists and is ready to use.
- Allocation — space is reserved in memory for the object's fields.
- Initialization — the constructor stores the passed-in values and opens any resources (a
db.connect(), a file handle, a socket). - Once the constructor returns, the object is alive and references to it can be handed around.
Life: in active use while referenced
An object stays alive as long as something points to it — a variable, a field on another object, an entry in a list. Call its methods, read its fields, pass it around; it keeps doing its job. The key idea is reachability: if your program can still get to the object through some chain of references, it must be kept alive.
Becoming unreachable: the last reference drops
The moment nothing references an object anymore, it becomes unreachable. The variable went out of scope, you reassigned it, or you removed it from a collection. The object is still sitting in memory, but your program has no way to reach it. It's now garbage — eligible for cleanup.
let c = new Connection("db://localhost"); // constructed, 1 reference
const also = c; // 2 references now
c.query("SELECT 1"); // active use
c = null; // 1 reference left (via 'also')
// still reachable through 'also' — NOT collected yet
// ...later...
// when 'also' goes out of scope too → 0 references → unreachableCleanup: destructor vs garbage collection
How the cleanup happens depends entirely on the language, and this is where the two big worlds split apart:
- Manual / deterministic (C++, Rust) — cleanup happens at a known moment. In C++, when an object goes out of scope, its destructor runs immediately and predictably. This powers RAII (Resource Acquisition Is Initialization): acquire a resource in the constructor, release it in the destructor, and scope exit guarantees cleanup.
- Garbage collected (Java, JavaScript, Python, Go, C#) — a garbage collector periodically finds unreachable objects and frees their memory for you. You don't call
delete. The catch: it runs whenever it decides to, not the instant the object becomes unreachable.
Finalizers are non-deterministic — don't rely on them
Python's __del__, Java's finalize(), and similar finalizers may run when the GC collects an object — but you can't predict when, and they might not run at all before the program exits. Never put critical cleanup (closing a file, releasing a lock) in a finalizer. Use try/finally, with, try-with-resources, or RAII instead — they guarantee cleanup at a known point.
So the lifecycle is universal — born, live, become unreachable, get reclaimed — but who triggers the final cleanup, and when, differs. In the prototype, Run GC stands in for that sweep: it walks the Unreachable lane, runs each object's finalizer, frees it, and removes the card.
Interactive prototype
See it. Build it. Break it.
A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.
About this simulation
Watch one object travel through four lanes — Constructed → Active → Unreachable → Reclaimed. Press new Object() to build one, use() it, drop reference until its count hits 0 so it slides to Unreachable, then Run GC to reclaim it. The console narrates every step.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Construct and use an object
Press new Object() to build a fresh Conn. Watch it appear in the Constructed lane (the constructor logs Conn#N constructed), then advance into Active with a reference count of 1. Hit use() a couple of times — the card pulses and the console logs each method call. This is an object doing its job while alive.
Drop the last reference
On an active object, press drop reference to decrement its count. Get it down to 0 and the card slides into the Unreachable lane — nothing in your program can reach it anymore. Notice it still exists in memory; it just isn't gone yet.
Reclaim with the garbage collector
Press Run GC (or wait for the automatic sweep). Every object in the Unreachable lane has its destructor/finalizer run — logged as GC: Conn#N finalized & reclaimed — and then fades out and disappears. Keep an eye on the live objects stat: it only counts objects that still exist.
In practice
When to use it — and what trips people up
Why lifecycle awareness matters
- Resource leaks — files, sockets, and DB connections opened in a constructor must be closed. The GC frees memory, but it won't promptly close your file handle. Forget to close and you'll run out of handles.
- Dangling / use-after-free — in manual-memory languages, touching an object after it's been freed is a crash or a security bug. Know when an object's life ends.
- Knowing when to free — pick the right tool: deterministic cleanup (
finally,with, RAII) for resources; let the GC handle plain memory.
Memory is reclaimed automatically; resources are not
Even in garbage-collected languages, treat external resources (files, sockets, locks) differently from memory. Memory gets cleaned up for you, eventually. Resources need an explicit, deterministic release — close them yourself the moment you're done.
What it gives you
- You know exactly when an object is safe to use and when it's gone — no use-after-free surprises.
- You release files, sockets, and locks at the right moment instead of leaking them.
- You can reason about memory pressure — why objects linger, and what keeps them alive (still-reachable references).
- You pick the right cleanup mechanism per language: RAII/
finally/withfor resources, GC for plain memory.
Common mistakes
- Resource leaks — opening a file/socket in a constructor and never closing it because 'the GC will handle it' (it won't, in time).
- Use-after-free / dangling references — keeping a pointer to an object after its lifetime ended.
- Relying on finalizers — putting cleanup in
__del__/finalize()and assuming it runs promptly, or at all. - Heavy constructors — doing slow or failure-prone work (network calls, big allocations) in the constructor, so creating an object becomes expensive or throws midway.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// JS/TS is garbage-collected — no manual delete, no reliable destructor.
class Connection {
private open = false;
constructor(public url: string) {
this.open = true;
console.log(`Conn(${url}) constructed — socket opened`);
}
query(sql: string) {
if (!this.open) throw new Error("use after close");
return `result of ${sql}`;
}
// GC won't reliably call this — close explicitly instead.
close() {
this.open = false;
console.log(`Conn(${this.url}) closed`);
}
}
let c: Connection | null = new Connection("db://localhost");
c.query("SELECT 1"); // active use
c.close(); // deterministic cleanup — do it yourself
c = null; // last reference dropped → unreachable
// the GC will reclaim the memory at some later, unspecified time.References & further reading
5 sources- Docsdocs.oracle.com
Oracle — Creating Objects
The Java tutorial on how
newallocates and the constructor initializes an object. - Articleen.wikipedia.org
Wikipedia — Object lifetime
A language-agnostic overview of creation, use, and destruction across runtimes.
- Docsdeveloper.mozilla.org
MDN — Memory management
How JavaScript decides an object is unreachable and how the garbage collector reclaims it.
- Docsdocs.python.org
Python docs — object.__del__
The reference for Python finalizers — and the warnings about why you can't depend on them.
- Articleen.wikipedia.org
Wikipedia — Resource Acquisition Is Initialization (RAII)
The C++ idiom that ties resource cleanup to object lifetime for deterministic release.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Nothing is scored or saved.
question 01 / 04
What runs exactly once when an object is first created, to set up its starting state?
question 02 / 04
In a garbage-collected language, when does an object become eligible for collection?
question 03 / 04
Why shouldn't you rely on Python's __del__ or Java's finalize() to close a file?
question 04 / 04
In C++, when does an object's destructor run for a local variable?
0/4 answered