A food delivery order is born when you tap "Place order". For the next 40 minutes it is busy: the restaurant accepts it, a rider picks it up, the app updates its status. Once it is delivered, nobody changes it any more, and some time later the system clears it from memory. Every object in your program goes through the same kind of journey. Understanding that journey, and who is responsible for each step of it, is what this chapter is about.

Object lifecycle = the journey of an object from the moment it is created, through the time it is used, until it is cleaned up and its memory is given back.

Real-world analogy: Think of a library membership card. It is issued after your details are checked (creation), you use it to borrow and return books (usage), and when you leave the city you surrender it so the library can close your account and reuse the card number (cleanup). A card that was never surrendered keeps your account open forever, even though nobody uses it. In code, that is a memory or resource leak.

1. What Is the Object Lifecycle?

Every object passes through the same broad phases:

  1. Creation. Memory is reserved and the constructor runs. Its job is to put the object into a valid starting state.
  2. Usage. Other code calls the object's methods. Its state changes over time, and other objects may hold references to it.
  3. End of use. The object goes out of scope, or the last reference to it is removed. From now on, nobody can reach it.
  4. Cleanup. Resources the object was holding (a file, a database connection, a lock) are released.
  5. Memory is reclaimed. In C++ this happens immediately when the object is destroyed. In Java, Python and JavaScript the runtime frees the memory automatically, at a time it chooses.

In Low Level Design you think about the lifecycle at two levels:

  • The language level: when is the constructor called, when is the object destroyed, and how do you make sure cleanup happens?
  • The domain level: which business states does the entity go through (an order goes from PLACED to DELIVERED), and which moves between states are allowed?

Both matter in interviews. A Parking Lot, Food Delivery or Library design is judged on whether its objects are created by the right class, move through sensible states, and release what they hold.

2. Creation: How an Object Is Born

Creation is handled by the constructor. A good constructor does three things:

  • Sets every field to a meaningful starting value.
  • Checks the input and refuses to build an invalid object (by throwing an exception).
  • Builds the parent part first when the class extends another class.

Here is an electric scooter in a bike-rental app. A scooter is a Vehicle, and every vehicle needs a number plate. The Vehicle constructor rejects an empty plate, and ElectricScooter decides its starting status from the battery level.

Output

Creating scooter TS09EV1234...
1. Vehicle part ready: TS09EV1234
2. ElectricScooter part ready: battery 85%, status AVAILABLE
Creating scooter TS09EV5678...
1. Vehicle part ready: TS09EV5678
2. ElectricScooter part ready: battery 12%, status NEEDS_CHARGING
Creating scooter with empty plate...
Creation failed: plate is required

Three things to notice:

  • Parent first, child second. Line 1 is always printed before line 2. A child object is built on top of a fully built parent, in every language. In Java and JavaScript the call to super(...) must come before the child uses this. In Python you call super().__init__(...) yourself. In C++ the base class constructor runs before the derived class's member initialisers.
  • A failed constructor means no object. For the empty plate, the exception stops construction, so no half-built scooter ever exists in the program. That is far safer than creating the object and calling a separate init() later, because nobody can forget to call it.
  • The starting state is decided once. A scooter with 12% battery starts as NEEDS_CHARGING. The rest of the system never sees a scooter without a status.

Where objects live after creation:

  • C++ gives you a choice. A stack object (ElectricScooter s("TS09EV1234", 85);) is destroyed automatically when its block ends. A heap object made with new lives until someone deletes it. In modern C++ you let a smart pointer such as std::unique_ptr do the deleting.
  • Java, Python and JavaScript always create objects on the heap and give you a reference to them. The object lives as long as at least one reference to it can still be reached.

3. Usage: The Object Moves Through States

Once created, most business objects do not just sit there. They move through a series of states, and only some moves make sense. A delivered order cannot go back to the kitchen. An order that is already out for delivery cannot be cancelled by the customer. If the class does not enforce these rules, every caller has to remember them, and sooner or later one of them gets it wrong.

The clean approach is to keep the allowed moves inside the class itself and let one method, moveTo(), be the only way to change the status:

Output

Order #101 created in state PLACED
#101: PLACED -> PREPARING
#101: PREPARING -> OUT_FOR_DELIVERY
#101: cannot go from OUT_FOR_DELIVERY to CANCELLED
#101: OUT_FOR_DELIVERY -> DELIVERED
Order #101 closed: true
#101: cannot go from DELIVERED to PREPARING
  • The rules live in one place. The ALLOWED table is the whole business lifecycle of an order. Adding a new state such as READY_FOR_PICKUP means changing one table, not hunting through the codebase.
  • Invalid moves are rejected instead of silently corrupting the object. Here they print a message. In a real system they would usually throw an exception.
  • Final states end the business lifecycle. After DELIVERED, the order is read-only. It may stay in memory (or in the database) for history, but it is "closed".

When the behaviour in each state becomes complex, this grows into the State design pattern, where each state is its own class. For most LLD problems, a transition table like this one is enough.

4. Destruction: How an Object Ends

This is where the four languages differ the most.

LanguageWhen is the object destroyed?Cleanup hookReliable way to release resources
C++Exactly when a stack object goes out of scope, or when a heap object is deletedDestructor ~ClassName()RAII: acquire in the constructor, release in the destructor
JavaSome time after it becomes unreachable, when the garbage collector decides to runNone you should rely on (finalize() is deprecated)AutoCloseable + try-with-resources
PythonUsually as soon as the last reference is gone (reference counting); cycles are freed later by the cycle collector__del__, but it is not guaranteed to runContext manager + with block
JavaScriptSome time after it becomes unreachable, when the garbage collector runsNone (FinalizationRegistry callbacks may never run)An explicit close() in try / finally

The key lesson: memory is freed for you, but other resources are not. A garbage collector reclaims memory. It does not close a file, return a database connection to a pool, or release a lock at a predictable time. For those, you need cleanup that runs exactly when you are done, even if an error happens along the way.

5. Guaranteed Cleanup, Even When Something Fails

A connection pool has a fixed number of database connections. Every job borrows one and must give it back. If a job crashes halfway and forgets to return the connection, the pool slowly runs dry and the whole app hangs. Here the borrowing is tied to the lifetime of a small Lease object, and each language's cleanup tool guarantees the connection is returned:

Output

Pool ready: 2 free
[report] got conn-1 (free: 1)
[report] running query
[report] returned conn-1 (free: 2)
[payment] got conn-1 (free: 1)
[payment] running query
[payment] returned conn-1 (free: 2)
[payment] failed: gateway timeout
Free connections at the end: 2

Look at the order of the last three lines for the payment job. The connection is returned before the error reaches the caller. That is exactly what you want: the cleanup runs on the way out, whether the block ended normally or because of an exception.

  • C++ (RAII): the destructor of Lease runs when runJob exits, including during exception unwinding. Copying is deleted so two leases can never return the same connection twice.
  • Java: any class that implements AutoCloseable can be used in try-with-resources, and its close() is always called.
  • Python: __exit__ runs when the with block ends. Returning False lets the exception carry on to the caller.
  • JavaScript: the finally block always runs. Newer runtimes also support using declarations with Symbol.dispose, which work like try-with-resources.

6. Ownership: Who Ends an Object's Life?

In a garbage-collected language an object stays alive as long as anything can still reach it. That is convenient, but it also means a forgotten reference keeps an object alive forever. This is the most common "memory leak" in Java, Python and JavaScript: not lost memory, but memory that is still referenced by mistake.

Output

Buggy manager: 3 users logged in
Buggy manager after 2 logouts: holding 3 session(s)
Fixed manager: 3 users logged in
Fixed manager after 2 logouts: holding 1 session(s)

The buggy manager only marks a session as inactive. The Session object is still inside the map, so it can never be freed. On a server with thousands of logins a day, that map grows until the server runs out of memory. The fixed manager removes the entry, so nothing points to the old sessions any more and the runtime can reclaim them. In C++ the same fix also runs the Session destructor immediately, because the map's unique_ptr deletes the object when the entry is erased.

In LLD, always decide for each class:

  • Who creates it? Usually the class that owns it, or a factory. A ParkingLot creates Tickets, a Cart creates its CartItems.
  • Who is allowed to keep a reference to it? Long-lived objects such as caches, registries, singletons and event listener lists are the usual places where references pile up.
  • What ends its life? A logout, a completed payment, a ticket being paid for at the exit gate. Make that event remove the object from every place that holds it.

These ownership questions lead straight into the class relationships in the next chapters: in association and aggregation the objects live independently, while in composition the owner controls the life of its parts.

7. Common Mistakes

  • Doing work outside the constructor that must always happen. A separate init() method that callers must remember to call leaves half-built objects around. Validate and set up everything in the constructor.
  • Relying on the garbage collector to release resources. Files, sockets, connections and locks need explicit cleanup with RAII, try-with-resources, with or finally.
  • Using finalize() or __del__ for important cleanup. They may run late or not at all.
  • Forgetting to remove objects from long-lived collections (caches, maps, listener lists). This keeps them alive forever.
  • Letting any caller change an entity's state freely. Put the allowed transitions inside the class.
  • In C++, using raw new and delete in normal code. Prefer stack objects and smart pointers, so destruction happens automatically.

8. Summary

PhaseWhat happensWhat you design
CreationMemory is reserved, the constructor runs (parent part first)Validation and a valid starting state
UsageMethods are called and the state changesAllowed state transitions inside the class
End of useOut of scope, or the last reference is removedClear ownership: who keeps references and who removes them
CleanupResources are releasedRAII, try-with-resources, with, or try / finally
Memory reclaimedC++: immediately. Java/Python/JS: by the runtimeNothing, as long as no stray references remain

Objects that are created valid, change state only through allowed moves, and release what they hold exactly when they are done make an LLD design reliable and easy to reason about.