Courses › Low-Level Design › Foundations
OOP & SOLID
Encapsulation, composition over inheritance and the five SOLID principles.
Low-level design interviews are judged on whether your classes are easy to understand, change and test. Object-oriented principles and SOLID are the vocabulary for that — use them to explain your decisions, not as a checklist to recite.
Core OOP ideas
- Encapsulation — keep state private and expose behavior. A
Spotshould offerassign(vehicle)andrelease(), not a publicoccupiedflag anyone can flip. - Abstraction — expose *what* something does, hide *how*. Callers use
PricingStrategy.price(ticket)without knowing the formula. - Polymorphism — treat different implementations uniformly through a shared interface.
- Composition over inheritance — build objects from smaller parts ("has-a") rather than deep class hierarchies ("is-a"). Inheritance couples subclasses tightly to parents; composition lets you swap parts at runtime.
# Inheritance explosion: GasCar, ElectricCar, GasTruck, ElectricTruck, ...
# Composition: a vehicle has a size and an engine.
class Vehicle:
def __init__(self, plate: str, size: "VehicleSize", engine: "Engine"):
self.plate = plate
self.size = size
self.engine = engineSOLID
S — Single Responsibility
A class should have one reason to change. A Ticket holds ticket data; computing the fee belongs to a pricing component; printing it belongs elsewhere.
O — Open/Closed
Open for extension, closed for modification. Add new behavior by adding new classes, not by editing a growing if/elif chain.
class PricingStrategy:
def price(self, ticket) -> int: ...
class HourlyPricing(PricingStrategy):
def __init__(self, cents_per_hour): self.rate = cents_per_hour
def price(self, ticket):
hours = max(1, ceil(ticket.duration_minutes() / 60))
return hours * self.rate
class FlatRatePricing(PricingStrategy):
def __init__(self, cents): self.cents = cents
def price(self, ticket): return self.centsL — Liskov Substitution
A subclass must be usable anywhere its parent is expected, without surprises. The classic violation: a Square subclass of Rectangle whose set_width also changes the height. If a subtype has to throw "not supported" for a parent method, the hierarchy is wrong.
I — Interface Segregation
Prefer several small, focused interfaces over one large one. A Printer and a Scanner interface, rather than MultiFunctionDevice forcing every class to implement fax().
D — Dependency Inversion
Depend on abstractions, not concrete classes, and inject dependencies. A ParkingLot receives a SpotAllocationStrategy in its constructor instead of creating NearestSpotStrategy itself. This is also what makes classes testable — pass in a fake clock, a fake repository.
Other habits interviewers notice
- Enums for fixed sets of values (
VehicleSize.SMALL), not strings - Immutable value objects for things like money and coordinates
- Clear error handling — raise specific exceptions or return explicit results, rather than silently failing
- Thread safety where shared state is mutated
[!INTERVIEW] Name the principle when you use it "I'm making allocation a strategy interface so we can add new allocation rules without touching ParkingLot — open/closed." One sentence like that shows you design intentionally.