Cheat sheet · Design Patterns and Low-Level Design (OOD) for Interviews

Design Patterns & LLD cheat sheet

The routine, the SOLID checks, the patterns with their trigger and trap, and the class shapes of the classic LLD problems. Say what hurts first, then name the pattern.

Read the full series →

blogs · jp-blogs.vercel.app/cheatsheets/series/low-level-design

The 5-step routineLesson 0 →

  1. Requirements (about 5 min): who uses it, what it must do, what is out of scope. Read 4 to 6 back.
  2. Entities: underline the nouns. Identity and life means a class; a small value is a value object.
  3. Class sketch: verbs become methods on the class that owns the data. Rules that may change go behind an interface.
  4. Key flow as real code: at least one path is working, not hand-waved.
  5. Extensions: show that a new rule is a small edit. Say a trade-off out loud.

Watch out: Do not name a pattern before the problem needs it. Say the pain first ("the fee rule will change"), then the pattern.

SOLID and small rulesLesson 0 →

LetterMeaningSmell
S Single responsibilityOne reason to changeClass formats, saves and calculates
O Open/closedAdd code, do not edit tested codeLong if / else if on a type
L LiskovA child works wherever the parent doesChild throws "not supported"
I Interface segregationSmall interfacesEmpty or throwing methods
D Dependency inversionDepend on an interface you ownnew SmtpMailer() in business code
  • Prefer composition ("has a") to inheritance ("is a"). Use extends only for a true is-a, when you own both classes.
  • Classes are nouns, methods are verbs. Avoid Manager, Helper, Util. Make small classes readonly.

Creational patternsLesson 1 →

You seeReach for
new X() in many files, kind chosen at run timeFactory (a Map registry stops the switch growing)
A matching set (theme, vendor)Abstract Factory
Many options with rules between themBuilder (else an options object)
"There must be one"Build one in the composition root, inject it
A big template object to duplicatePrototype: structuredClone or clone()
Code needs a serviceConstructor injection

Watch out: Singleton traps: hidden dependency, shared test state, not one across processes or module URLs. Spread {...o} is a shallow copy.

Structural patternsLesson 2 →

PatternIntentWrapper interface
AdapterMake a thing fit the shape I wantDifferent
DecoratorAdd logging, cache, retrySame
FacadeOne simple door, with undo on failureNew, smaller
ProxyLazy load, permission, remoteSame
CompositeLeaf and group look the same (tree)Same
  • Decorator is chosen by the caller and the inner object always exists. Proxy may never touch the real object.
  • Stack order matters: WithLog(WithCache(WithRetry(base))) caches the final answer. Cache inside retry checks the cache on every attempt.
  • Keep add() on the composite only, so a leaf cannot be asked to add children.

Behavioural patternsLesson 3 →

Your needPattern
A rule that changes or differs per caseStrategy (often a function)
Many things react to XObserver
Undo, queue, retry, auditCommand (do() and undo())
Behaviour depends on a changing statusState
A request passes checks or levelsChain of Responsibility
Walk a collection, lazilyIterator (a generator method)
Same steps, few differTemplate Method, or Strategy
  • Strategy vs State: the caller picks a strategy; a state is the object's own condition and picks the next state.
  • Observer: subscribe returns an unsubscribe function, loop over a copy, catch errors per listener.
  • Command vs Strategy: a command is one request with data; keep it if you need history.

Patterns in real codeLesson 4 →

Express / Koa middleware
Chain of Responsibility with an onion: code after next() is a decorator
EventEmitter
Observer. Sync listeners, error with no listener throws, leaks
Redux
Action is a command as data, reducer is pure, subscribe is Observer
Custom hook
Reuse by composition, no base class
React Context
Dependency injection, not a store; all consumers re-render
JS Proxy
Dynamic decorator or guard; a trap runs on every access

Watch out: Request hangs? A middleware neither responded nor called next(), or called it twice, or forgot await next().

Anti-patternsLesson 4 →

SmellFix
God object (validate, save, email)Split by reason to change; facade only coordinates
Premature abstraction (flags, "kind")Rule of three: abstract on the third repeat
Singleton abuseCreate once, inject
Anemic model (rules in services)Put rules next to the data
Over-engineeringPlain function now, pattern when case two is real

Parking lotLesson 5 →

class Spot { fits(v); claim(plate);
  release() }
interface AllocationStrategy {
  choose(spots): Spot | undefined }
interface PricingStrategy {
  feeCents(v, minutes): number }
class HourlyPricing // rounds up
class DailyCap // decorates pricing
class ParkingLot {
  constructor(spots, alloc,
    pricing, now)
  park(v): Ticket | null
  unpark(id, v): { feeCents,
    minutes }
}

Vehicle and Ticket are plain data. Money in integer cents, time from an injected clock.

AllocationCost
First freeWastes large spots on small vehicles
Nearest to entranceTrucks lose large spots
Best fit (smallest that fits)Motorcycle may get a far spot
Spread by levelMore walking

Tip: Check-then-act is a race. Make it one step: SQL UPDATE ... WHERE plate IS NULL (1 row), Redis SET NX.

Hash map and LRU cacheLesson 6 →

  • Hash map: buckets plus a hash function; chaining on collision. Resize at load factor about 0.75 and rehash every key (hash % buckets changes).
  • LRU = Map from key to node + doubly linked list in use order. Sentinel head and tail remove edge cases.
  • Store the key in the node, so evicting the tail can call map.delete(node.key).
  • TTL: store expiresAt, check on get, inject the clock. Lazy expiry is simplest; a sweep frees memory sooner.
ConcernAnswer
Stampede on one keyShare the in-flight promise per key
Threadsget mutates the list: one lock, or shard by key hash
Many serversShared cache such as Redis
LRU vs LFU vs FIFOLRU is default; a scan can flush it

State machinesLesson 7 →

  1. Name the states and events, then write the transition table first.
  2. Every cell is a move or "not allowed". Prefer one state value to several booleans.
  3. Pick the style: State pattern (object per state) for rich behaviour, table for mostly "go to X".
  4. Failures are results; an invalid transition throws. Refund first, then change state.
Elevator schedulerRuleWeak spot
First come first servedOldest requestZig-zags (19 floors in the demo)
SCANKeep direction, stop on path, then turnFar opposite request waits (8 floors)

Cards, chat server, call centerLesson 8 →

  • A Card is data. BlackjackHand scores, since an ace depends on the hand: count aces as 1, add 10 once if no bust.
  • Shuffle with Fisher-Yates and inject the random source. Test rounds with stacked decks.
  • Chat: User, FriendRequest (pending to accepted or rejected, once), Chat (members, messages), ChatServer enforces cross-object rules.
  • Online delivery is Observer. Unread is a read position per user per chat. Order comes from per-chat ids.
  • Call center: Chain of Responsibility (operator, supervisor, director) plus a queue when all are busy.

Rate limiterLesson 9 →

interface RateLimiter {
  allow(key, cost = 1): Decision
}  // allowed, remaining, retryAfterMs
class TokenBucket(cap, refill, now)
class FixedWindow(limit, ms, now)
class SlidingWindowLog(limit, ms)

Return retryAfterMs so the HTTP layer can send 429 and Retry-After.

AlgorithmState per keyWeak spot
Token bucket2 numbersBig bucket, big burst
Fixed window1 counterUp to 2x limit at a boundary
Sliding logTimestampsMemory grows with limit
Sliding counter2 countersAn estimate

Tip: Many servers: shared store with an atomic update, and decide fail open or fail closed. Delete idle full buckets.

Notification serviceLesson 9 →

EventBus
Observer: producers do not know who listens
Channel per medium
Strategy: add Slack with one class
templates
Data: text changes without code
withRetry
Backoff, cap, jitter, skip PermanentError
done keyed eventId:channel
Idempotency; store the promise for concurrent copies
deadLetters
Failures kept, not lost
  • Wait before attempt n: random between 0 and min(cap, base * 2^n). Jitter stops a synchronised retry storm.
  • Crash after send, before record, gives at-least-once. Pass your key to the provider to get closer to once.

Interview one-linersLesson 0 →

QuestionShort answer
Adapter vs Decorator?Adapter changes the interface; decorator keeps it and adds behaviour.
Decorator vs Proxy?Decorator adds a feature; proxy controls access to the real object.
Strategy vs State?Caller picks a strategy; a state changes itself.
Factory Method vs Abstract Factory?One product via an overridable step, vs a matching family.
Inversion vs injection?Inversion is the rule; injection is passing it in, often by constructor.
Observer vs pub/sub?Pub/sub adds a broker between them and can cross processes.
Undo and redo?Commands with undo(); undo and redo stacks; a new command clears redo.
Why a doubly linked list?Move or remove a node in O(1) needs its previous node.

A cheat sheet is a reminder, not a teacher. Each card links to the lesson that explains it step by step.