Design patterns and low-level design
The whole track is free.
Object-oriented foundations
Classes, objects, and the handful of ideas every pattern is built from — encapsulation, composition, polymorphism, interfaces, and how tightly things should depend on each other.
- Classes and ObjectsA class is a blueprint for data plus the behaviour that keeps it valid; an object is one live instance of it.
- EncapsulationHide an object's data behind a small set of methods, so its rules can't be bypassed and its insides can change freely.
- Inheritance vs CompositionIs-a versus has-a — when to extend a class, and why most designs are better off assembling objects from parts.
- PolymorphismOne call, many behaviours — code that talks to an interface works with every class that implements it, including ones written later.
- Interfaces and Abstract ClassesContracts without (or with only some) implementation — how you say what a role must do before anyone decides how.
- Coupling and CohesionThe two dials behind every design judgement — how much modules depend on each other, and how well each one sticks to one job.
SOLID and design principles
Five principles for code that bends instead of breaking, plus the three rules of thumb (DRY, KISS, YAGNI) that keep you from overdoing them.
- Single Responsibility PrincipleA class should have one reason to change — split code by the people and pressures that change it.
- Open/Closed PrincipleAdd new behaviour by adding code, not by editing code that already works.
- Liskov Substitution PrincipleA subclass must keep every promise its parent makes, so code written for the parent works unchanged with the child.
- Interface Segregation PrincipleMany small, role-shaped interfaces beat one fat one — no class should be forced to depend on methods it doesn't use.
- Dependency Inversion PrincipleHigh-level policy shouldn't depend on low-level details — both depend on an abstraction the policy owns, and the details get plugged in.
- DRY, KISS, and YAGNIThree rules of thumb that keep the principles honest — one source of truth, the simplest thing that works, and nothing you don't need yet.
Creational patterns
Five ways to decide who creates an object and how, so the code that uses it doesn't have to know.
- Factory MethodLet subclasses decide which class to create, so the code that uses the product never names a concrete type.
- Abstract FactoryOne object that creates a whole family of matching parts, so you can never mix pieces from two families by accident.
- BuilderConstruct a complex object step by step, with readable calls instead of a constructor with twelve parameters.
- PrototypeCreate new objects by copying a configured sample, without knowing (or re-running) how it was built.
- SingletonGuarantee a class has exactly one instance with a global access point — and why it's the most overused pattern of all.
Structural patterns
Seven ways to assemble objects into bigger structures — wrapping, adapting, sharing, and simplifying — without welding them together.
- AdapterWrap an incompatible class so it fits the interface your code already expects — no edits on either side.
- BridgeSplit one class hierarchy that varies two ways into two hierarchies joined by composition, so each side grows independently.
- CompositeTreat a single item and a whole tree of items the same way, so client code never asks "is this a leaf or a group?"
- DecoratorAdd responsibilities to an object by wrapping it in another object with the same interface — stackable, and chosen at runtime.
- FacadeOne simple front door to a complicated subsystem, so most callers never need to learn its internals.
- FlyweightShare the heavy, unchanging part of many similar objects, and pass the small varying part in from outside.
- ProxyA stand-in with the same interface as the real object, controlling access to it — lazily, remotely, cautiously, or with a cache.
Behavioral patterns
Eleven ways for objects to divide up work and talk to each other without every one knowing about every other.
- Chain of ResponsibilityPass a request along a line of handlers until one deals with it — the sender never needs to know which one will.
- CommandTurn a request into an object — so it can be queued, logged, retried, and, best of all, undone.
- InterpreterRepresent each rule of a tiny language as a class, build sentences as trees of them, and evaluate by walking the tree.
- IteratorWalk the items of any collection one at a time, without knowing whether it's a list, a tree, or pages from an API.
- MediatorReplace a tangle of objects talking to each other with one coordinator they all talk through.
- MementoCapture an object's state as an opaque snapshot so it can be restored later — without exposing its insides.
- ObserverLet any number of objects subscribe to an event and be told when it happens, without the publisher knowing who they are.
- StateLet an object change its behaviour when its internal state changes, by delegating to a state object instead of switching on a status field.
- StrategyPut each interchangeable algorithm behind one interface, and let the caller pick which one to plug in.
- Template MethodWrite an algorithm's skeleton once in a base class and let subclasses fill in only the steps that differ.
- VisitorAdd new operations to a fixed set of classes without editing them, by moving each operation into a visitor object.
Code smells
The warning signs that a design is drifting, grouped into five families — each with a broken program to refactor until it's right.
- Smells: BloatersCode that has grown too big to handle — long methods, huge classes, long parameter lists, and data that should have been an object.
- Smells: Object-Orientation AbusersOO features used half-heartedly or wrongly — repeated type switches, fields that are only sometimes set, subclasses that refuse their inheritance.
- Smells: Change PreventersDesigns where one change forces many edits — one class changing for every reason, or one reason touching every class.
- Smells: DispensablesCode you'd be better off without — duplicates, dead code, comments covering for unclear code, classes that do too little, and generality nobody uses.
- Smells: CouplersClasses that know too much about each other — envying another's data, reaching into its internals, chaining through strangers, or just passing calls along.
Refactoring techniques
Small, safe, behaviour-preserving changes — the moves that turn a smell into clean code, one step at a time.
- Refactoring SafelyChange a program's structure without changing what it does — in small, tested steps you can stop between at any time.
- Refactoring: Composing MethodsExtract, inline, and name — the moves that turn long functions into short ones that read like their own documentation.
- Refactoring: Moving FeaturesMove methods and fields to the class that owns the data, split classes that do too much, and decide how much to hide behind a delegate.
- Refactoring: Organizing DataGive raw values names, types, and rules — replace magic numbers, wrap primitives, encapsulate fields and collections, and turn type codes into classes.
- Refactoring: Simplifying ConditionalsTame nested ifs with guard clauses, named conditions, merged branches, polymorphism, and null objects.
- Refactoring: Simplifying Method CallsMake interfaces easy to call correctly — clear names, fewer and better parameters, queries separated from commands, and factories instead of tricky constructors.
- Refactoring: Dealing with GeneralizationMove behaviour up and down class hierarchies, extract shared parents and interfaces, and swap inheritance for delegation when it stops fitting.
Low-level design case studies
The machine-coding and LLD rounds interviewers ask, worked end to end — requirements, entities, class diagram, key flows, a runnable skeleton, and the extensions they'll push on.
- How to Approach an LLD RoundA repeatable six-step method for low-level design interviews — from a vague prompt to classes, flows, and working code in under an hour.
- LLD: Parking LotLevels, spot sizes, tickets, and fees — the classic LLD warm-up, with spot allocation and pricing as swappable strategies.
- LLD: Elevator SystemSeveral cars, many floors, and a dispatcher deciding who goes where — state per car, a scheduling strategy, and a step-by-step simulation.
- LLD: Vending MachineCoins in, product out, change back — a textbook State-pattern design where every button means something different depending on what the machine is doing.
- LLD: LRU CacheO(1) get and put with least-recently-used eviction — a hash map for lookup plus a doubly linked list for recency, behind a clean cache interface.
- LLD: Rate LimiterAllow N requests per client per period — token bucket and sliding window as interchangeable algorithms behind one limiter interface, with a testable clock.
- LLD: Library Management SystemBooks versus copies, members, loans, holds, and fines — a domain-modelling exercise where getting the nouns right is most of the work.
- LLD: ChessBoard, pieces, moves, and turns — polymorphic pieces that know how they move, a game that knows the rules around them, and moves as objects you can undo.
- LLD: Expense SplitterGroups, shared expenses, equal/exact/percentage splits, net balances, and the fewest payments to settle up — split rules as strategies, money as integer cents.
- LLD: Movie Ticket BookingCinemas, shows, seat maps, and the hard part — holding seats for a few minutes so two people can never buy the same one.
- LLD: Logging FrameworkLoggers, levels, handlers, and formatters — a small framework that shows Singleton, Chain of Responsibility, Strategy, and Observer working together.