Design Patterns (GoF and modern) Module 11 – Choosing patterns in design reviews and LLD rounds
Choosing a pattern: a problem-first map
Choose a design pattern from what varies in a requirement, eliminate candidates by their costs, and name the patterns inside classic LLD problems.
What you will learn
- Start from what varies in a requirement and map it to candidate patterns
- Eliminate candidates whose consequences do not fit the constraints
- Explain a choice and its rejected alternative in two sentences
- Name the patterns inside classic LLD problems such as a parking lot, an elevator and a vending machine
Before you start
What you’ll learn · Before you start
What you will learn
- Start from what varies in a requirement and map it to candidate patterns
- Eliminate candidates whose consequences do not fit the constraints
- Explain a choice and its rejected alternative in two sentences
- Name the patterns inside classic LLD problems such as a parking lot, an elevator and a vending machine
Before you start
On this page
Choose a design pattern from the problem, not from the catalogue. Read the requirement and find what varies, or what must be protected; ask which of four questions that variation answers; list the few patterns that answer it; then eliminate the candidates whose consequences your constraints cannot pay. Finish by saying what you chose and what you rejected, and why. Picking by keywords skips every one of these steps: “notifications” does not mean Observer, and “there is only one” does not mean Singleton.
The classic catalogue points the same way. The first chapter of Design Patterns (Gamma, Helm, Johnson and Vlissides, 1994) has a table of the design aspects that each pattern lets you vary. For Strategy it is an algorithm, for State the states of an object, and for Observer the number of objects that depend on another one. The map in this lesson is MySmartCoPilot’s own and is organised by question, so that it works on a requirement you have never seen before.
This lesson walks ten requirements through the map, measures one decision in code that runs, and names the patterns inside five classic low-level design (LLD) problems.
Start from what varies
Every pattern protects one kind of change. Strategy lets an algorithm change without touching the code that uses it; State lets behaviour change with an object’s own condition; Adapter lets a class you cannot edit fit an interface you need. So the first question about a requirement is not “which pattern is this?” but “what will be different next month, between two customers, or between two runs?” For failures, the question is “what must keep working when something else breaks?”
Write the variation down as a short phrase, such as “the fee rule, per city”, “what an article allows, by its status” or “who hears about a price change”. Then ask which of four questions it answers:
The question map: from what varies to a pattern you can defend
Text description of the diagram
A flow chart read from top to bottom.
It starts with a requirement, which leads to the question "What varies, or must be protected?".
That leads to a group of four questions, each with the patterns it suggests. "Which object to create?" suggests Factory Method, Abstract Factory, Builder and Prototype. "How do objects fit, wrap or share?" suggests Adapter, Facade, Decorator, Proxy and Composite. "Who decides, and how do objects talk?" suggests Strategy, State, Command, Observer and Mediator. "How does the code survive failure?" suggests retry with backoff, a circuit breaker, timeouts and bulkheads.
From the group, the flow continues to "Eliminate candidates by their consequences", and ends with "Say the choice and the rejected alternative".
- Which object to create? The variation is in what gets made, or how: one product picked by a subclass or a function (Factory Method), families of objects that must match (Abstract Factory), one complex object built in steps (Builder), copies of a configured original (Prototype).
- How do objects fit, wrap or share? The variation is in how objects are put together: an interface you cannot change (Adapter), one simple front for many classes (Facade), behaviour added in layers (Decorator), access that is checked or delayed (Proxy), trees of parts used like a single part (Composite).
- Who decides, and how do objects talk? The variation is in behaviour and messages: an algorithm picked from outside (Strategy), actions that depend on the object’s condition (State), requests that are queued or undone (Command), dependents that must hear about a change (Observer), peers that talk through a hub (Mediator).
- How does the code survive failure? The variation is in what goes wrong: brief failures that a retry with backoff absorbs, long outages that a circuit breaker waits out, calls that need a timeout, and work kept apart in bulkheads.
The first three questions follow the three purposes the classic catalogue sorts its patterns by: creational, structural and behavioral. The fourth gathers the resilience patterns that services added later, which this track covers in its own module.
From a variation to candidates
The question narrows the catalogue to a few candidates. This table maps the variations that requirements most often contain to those candidates, and to the consequence to check before you pick one:
| What varies | Candidates | Check before choosing |
|---|---|---|
| An algorithm, picked by the caller or by a setting | Strategy, or a function passed as an argument | Does the caller know which one to use? |
| Behaviour, by the object’s own condition | State | Does the object move itself from state to state? |
| Who reacts to a change | Observer, an event bus | The order of calls, a failing listener, removing listeners |
| Which class to make | Factory Method, a dictionary of classes | Is there a second product today? |
| Families of objects that must match | Abstract Factory | A new kind of product touches every factory |
| One object with many optional parts | Builder, keyword arguments | Would a constructor with defaults do? |
| An interface you cannot change | Adapter | Does the adapter translate errors, or hide them? |
| Behaviours combined per object | Decorator | The order of the wrappers changes the result |
| Requests that are queued, logged or undone | Command | Does anything need undo or replay? |
| A dependency that is slow or fails | Timeout, retry with backoff, circuit breaker | Is the call safe to repeat? |
Two rows name a language feature next to the pattern on purpose. In Python, a function passed as an argument is
already a strategy: sorted(orders, key=by_amount) takes the comparison as a function, so a dictionary from the
user’s choice to a key function can be the whole design. A pattern describes roles, and a language feature can fill a
role without a class hierarchy.
Eliminate by consequences
Every pattern adds structure and has a known downside; the catalogue calls these its consequences and lists them next to each pattern’s structure. The candidates that a question leaves are compared on those costs:
- Decorator makes behaviour stackable, and makes the order of the wrappers part of the behaviour.
- Observer frees the subject from knowing its dependents, and makes it answer for the order of notification, for a subscriber that fails and for unsubscribing, where memory leaks start.
- State removes the switches on a status field, and adds a class per state, with the transitions spread across them.
- Visitor makes a new operation cheap, and a new element type expensive, because every visitor must learn it.
- Abstract Factory keeps families consistent, and turns a new kind of product into an edit to every factory.
- Singleton gives one shared instance, and hidden global state that every test shares.
The project’s constraints then decide between the survivors. A script that two people maintain cannot carry five classes for a variation that may never arrive. A payment path that must never charge twice can carry idempotency keys and a state machine, because a double charge costs far more than the classes do.
Ten requirements, worked
Each requirement goes through the same steps: what varies, the choice, and the alternative rejected, with the reason.
- Fees by city. “Fees are worked out by distance, but the operations team switches single cities to a flat fee or
a surge fee on some days, without a release.” The fee rule varies and configuration picks it per city:
Strategy, one object or function per rule. Rejected: an if-chain inside
fee(), which every new rule would edit, with every city tested again. - An article workflow. “An article moves from draft to review to published; editing a published article creates a new draft, and an archived one refuses every change.” What each action does depends on the article’s status, and the article moves itself between statuses: State. Rejected: Strategy, because nobody outside the article picks its behaviour.
- Price alerts. “When a share price changes, the chart, each user’s alerts and the audit log must update, and plugins may add more.” The set of dependents varies: Observer, with order, errors and unsubscribing settled up front. Rejected: three direct calls from the price feed, which would change for every plugin.
- Payment providers. “Checkout works with three payment providers whose SDKs name their calls differently, and a fourth is planned.” Interfaces you cannot change must fit one interface you choose: an Adapter per provider, picked by configuration. Rejected: a Facade alone, which can simplify one SDK but cannot make three interchangeable.
- Endpoint policies. “Some endpoints need retries, some caching, some logging, and some all three.” Behaviours are combined per endpoint: Decorator, with a test that fixes the order. Rejected: a subclass per combination, seven classes for three behaviours.
- Undo in an editor. “A drawing editor needs undo and redo for every edit, and macros that replay a list of edits.” Requests must be stored, reversed and replayed: Command, each edit an object that can do and undo itself. Rejected: a full copy of the drawing after every edit, which is simpler but grows with every edit.
- Report settings. “A report has twelve optional settings, a date range needs both of its ends, and a report never changes once it is made.” One complex object is assembled and checked before it exists: Builder, or in Python, keyword arguments checked by the constructor. Rejected: a long positional constructor, where two swapped arguments are a silent bug.
- A flaky partner. “A partner’s stock API fails for a few seconds several times a day, and was down for an hour last month.” The length of the failure varies: a timeout on every call, retries with backoff and jitter for short failures, and a circuit breaker for long ones. Rejected: retries alone, which add load to a partner that is already down.
- One export format. “An internal tool exports one CSV format for one team; a pull request adds an Exporter interface, a factory and a registry for future formats.” Nothing varies: no pattern, one function. The seam arrives with a second format that has a user and a date.
- A global config. “A config object is read once at start-up; someone proposes a Singleton so that functions need not take it as a parameter.” Nothing varies here either, and the proposal adds hidden global state: no pattern. Keep passing the config to the objects that need it.
The last two are the ones people get wrong in reviews. A pattern needs a variation, and a requirement without one needs plain code; “there is only one” is a fact about the program, not a reason for Singleton.
One decision, measured: the lift
A lift puts two variations into one class, which is why it gets two patterns. What the car does on each tick depends on
its own condition: idle, moving, or with its doors open. That is State: three small classes, each of which decides
the next one. How the car picks its next stop is a policy that a building may want to change. That is Strategy:
an object with one method, next_stop(car), which the car calls without knowing which policy it holds.
The program plays one morning in a ten-floor office with two schedulers. Someone on floor 9 calls first; then people keep calling from the low floors:
Python · elevator/compare.py
from elevator import run
from schedulers import FirstCome, NearestFirst
# One morning in a ten-floor office (floors 0 to 9), as (tick, floor) calls: floor 9 first, then the low floors.
MORNING = [(0, 9), (2, 2), (3, 7), (5, 2), (8, 3), (11, 0), (13, 4), (15, 3), (17, 1)]
print("scheduler floors average wait longest wait")
for scheduler in (FirstCome(), NearestFirst()):
car = run(scheduler, MORNING)
average = sum(car.waits) / len(car.waits)
print(f"{type(scheduler).__name__:<12}{car.moved:>8}{average:>14.1f}{max(car.waits):>14}")
print(f" stops: {' '.join(map(str, car.stops))}")
car = run(NearestFirst(), MORNING)
print("NearestFirst served floor 9 last, after", car.waits[-1], "ticks") Output
scheduler floors average wait longest wait FirstCome 35 20.3 30 stops: 9 2 7 3 0 4 1 NearestFirst 17 9.0 33 stops: 2 2 3 0 1 3 4 7 9 NearestFirst served floor 9 last, after 33 ticks
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 compare.py
elevator/elevator.py
"""A lift car: its own behaviour is State (Idle, Moving, DoorsOpen); how it picks the next stop is a Strategy."""
from dataclasses import dataclass
@dataclass(frozen=True)
class Call:
floor: int
at: int # the tick when someone pressed the button
class Idle:
"""Doors closed and nobody waiting: nothing happens until a call arrives."""
def step(self, car, now):
return Moving().step(car, now) if car.waiting else self
class Moving:
"""On every tick the car asks its scheduler for the next stop, then moves one floor or opens its doors."""
def step(self, car, now):
stop = car.scheduler.next_stop(car)
if stop is None:
return Idle()
if stop == car.floor:
car.open_doors(now)
return DoorsOpen()
car.direction = 1 if stop > car.floor else -1
car.floor += car.direction
car.moved += 1
return self
class DoorsOpen:
"""The doors take one tick to close; then the car moves on, or rests."""
def step(self, car, now):
return Moving() if car.waiting else Idle()
class Car:
def __init__(self, scheduler, floor=0):
self.scheduler = scheduler # the Strategy: any object with next_stop(car)
self.state = Idle() # the State: Idle, Moving or DoorsOpen
self.floor = floor
self.direction = 0 # +1 up, -1 down, 0 before the first move
self.waiting = [] # calls not served yet
self.stops = [] # floors where the doors opened, in order
self.waits = [] # how many ticks each served call waited
self.moved = 0 # floors travelled
def call(self, floor, at):
self.waiting.append(Call(floor, at))
def open_doors(self, now):
here = [c for c in self.waiting if c.floor == self.floor]
self.waiting = [c for c in self.waiting if c.floor != self.floor]
self.waits += [now - c.at for c in here]
self.stops.append(self.floor)
def step(self, now):
self.state = self.state.step(self, now)
def run(scheduler, calls, start=0, limit=500):
"""Plays (tick, floor) calls on a car with this scheduler until every call is served."""
car = Car(scheduler, start)
script = sorted(calls)
now = 0
while now < limit and (script or car.waiting or not isinstance(car.state, Idle)):
while script and script[0][0] == now:
car.call(script.pop(0)[1], now)
car.step(now)
now += 1
return car elevator/schedulers.py
"""Two ways to choose the next stop. Each is a Strategy: the car calls next_stop(car) and never asks which it has."""
class FirstCome:
"""The oldest call first, wherever it is."""
def next_stop(self, car):
if not car.waiting:
return None
return min(car.waiting, key=lambda c: (c.at, c.floor)).floor
class NearestFirst:
"""The closest call first, however long anyone else has waited."""
def next_stop(self, car):
if not car.waiting:
return None
return min(car.waiting, key=lambda c: (abs(c.floor - car.floor), c.at, c.floor)).floor Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Neither scheduler is acceptable. FirstCome serves calls in the order they came, so it travels 35 floors and the average wait is 20.3 ticks. NearestFirst travels only 17 floors with an average wait of 9.0, but it keeps finding someone closer than floor 9, so the person who called first waits 33 ticks, longer than anyone waits under FirstCome. Which trade-off is right differs between a hospital and an office, and that is the variation Strategy protects: the car’s code did not change between the two runs.
A third policy, Sweep, keeps the car moving in one direction while anyone waits ahead of it, and only then turns around. In the exercise you write it and plug it into the same car. On the same morning it travels 18 floors, one more than NearestFirst, and nobody waits more than 21 ticks.
Patterns inside classic LLD problems
Low-level design rounds reuse a handful of problems, and each one hides the same few variations. The LLD track works through these problems in full. This list names the patterns each one usually earns, and the one that often arrives without a reason:
- Parking lot: Strategy for the pricing rules, a factory for the kinds of spot, and Observer for the boards that show free spots. Often added without a reason: a Singleton for the lot.
- Elevator: State for the car’s own behaviour, Strategy for choosing the next stop, and Command for calls that can be cancelled. Often added without a reason: a class per floor.
- Vending machine: State for what each button does when idle, paid, dispensing or sold out. Often added without a reason: a Strategy for payment, with one way to pay.
- Logger: Chain of Responsibility, or a list of handlers, for which messages pass, and Strategy for where they go. Often added without a reason: a Singleton that tests cannot replace.
- Notification service: Observer or an event bus for who gets what, Strategy per channel, and Builder for putting a message together. Often added without a reason: one service that knows every channel.
Interview formats differ between companies, teams and countries. What holds in all of them is that an interviewer can follow only the reasoning you say aloud, which is why the last step of a choice is a sentence.
Say it like a reviewer
A choice is complete when it names four things: the variation, the pattern, what the pattern costs, and the alternative you rejected, with the reason. For the lift, that is two sentences per decision:
- “How the next stop is chosen varies by building, so the scheduler is a Strategy. It costs one class per policy, and I rejected rules inside the car because every new policy would edit the class that also runs the doors.”
- “The car’s behaviour depends on whether it is idle, moving or open, so the car uses State. It costs three small classes, and I rejected a status field with a switch in every method because a new condition, such as a maintenance mode, would edit all of them.”
Each pair survives a follow-up question, because the variation is named. A choice without one, such as “I used Strategy because it is flexible”, does not: flexible along which axis, and who asked for it?
Practise: plug a scheduler into the lift
The car in the sample tests is the one from the example, unchanged. Your scheduler is a new class, and the car never learns its name.
Exercise · Medium · Python, JavaScript
Write the Sweep scheduler and leave the car as it is
The lift car in the sample tests is finished: its states (Idle, Moving, DoorsOpen) decide what it does on each tick, and on every tick while it moves it asks its scheduler for the next stop. You write the scheduler, and you do not touch the car.
Write Sweep, with one method, next_stop(car) (in JavaScript, nextStop(car)), that returns the floor of the next stop, or None (null) when nobody is waiting:
- A car that has not moved yet (
directionis 0) goes to the nearest call; if two are equally near, the one made first. - A moving car keeps its direction while anyone waits ahead of it, or on its own floor, and stops at the nearest of those calls.
- When nobody waits ahead, it turns around and goes to the nearest call behind it.
The car gives you car.floor, car.direction (+1 up, −1 down, 0 before the first move) and car.waiting, the calls not served yet, each with .floor and .at, the tick when it was made. The tests play seven scripts. On the morning of the lesson, your scheduler should travel 18 floors and keep everyone's wait at 21 ticks or less.
Python · Starter code · sweep.py
"""The lift's next-stop Strategy. The car (in the sample tests) calls next_stop(car) on every tick while it moves."""
class Sweep:
"""Keep going while anyone waits ahead or on this floor; then turn around. A car that has not moved yet goes
to the nearest call."""
def next_stop(self, car):
# car.floor: where the car is; car.direction: +1 up, -1 down, 0 before the first move
# car.waiting: the calls not served yet, each with .floor and .at (the tick of the call)
if not car.waiting:
return None
return car.waiting[0].floor # the first call in the list: replace this with the three rules The sample tests · test_sweep.py
from dataclasses import dataclass
from sweep import Sweep
# ---- the lift car of the lesson, unchanged: its states decide, the scheduler only picks the next stop ----
@dataclass(frozen=True)
class Call:
floor: int
at: int
class Idle:
def step(self, car, now):
return Moving().step(car, now) if car.waiting else self
class Moving:
def step(self, car, now):
stop = car.scheduler.next_stop(car)
if stop is None:
return Idle()
if stop == car.floor:
car.open_doors(now)
return DoorsOpen()
car.direction = 1 if stop > car.floor else -1
car.floor += car.direction
car.moved += 1
return self
class DoorsOpen:
def step(self, car, now):
return Moving() if car.waiting else Idle()
class Car:
def __init__(self, scheduler, floor=0):
self.scheduler, self.state, self.floor, self.direction = scheduler, Idle(), floor, 0
self.waiting, self.stops, self.waits, self.moved = [], [], [], 0
def call(self, floor, at):
self.waiting.append(Call(floor, at))
def open_doors(self, now):
here = [c for c in self.waiting if c.floor == self.floor]
self.waiting = [c for c in self.waiting if c.floor != self.floor]
self.waits += [now - c.at for c in here]
self.stops.append(self.floor)
def step(self, now):
self.state = self.state.step(self, now)
def run(calls, start=0, limit=500):
car = Car(Sweep(), start)
script = sorted(calls)
now = 0
while now < limit and (script or car.waiting or not isinstance(car.state, Idle)):
while script and script[0][0] == now:
car.call(script.pop(0)[1], now)
car.step(now)
now += 1
return car
# ---- seven scripts of (tick, floor) calls ----
def test_a_new_car_goes_to_the_nearest_call():
"""rule 1: from floor 5, floor 7 is nearer than floor 2"""
car = run([(0, 7), (0, 2)], start=5)
assert car.stops == [7, 2], f"stops {car.stops}"
def test_a_tie_goes_to_the_call_made_first():
"""rule 1: the car served floor 5 without moving; floors 3 and 7 are equally near, and 3 called first"""
car = run([(0, 5), (1, 3), (2, 7)], start=5)
assert car.stops == [5, 3, 7], f"stops {car.stops}"
def test_doors_open_at_once_for_a_call_on_the_car_floor():
"""a call on the car's own floor is served on the first tick"""
car = run([(0, 4), (0, 1)], start=4)
assert car.stops == [4, 1], f"stops {car.stops}"
assert car.waits[0] == 0, f"waits {car.waits}"
def test_stops_for_a_call_on_a_floor_it_is_passing():
"""rule 2: on its way up to 8, the car is at floor 3 when floor 3 calls"""
car = run([(0, 8), (3, 3)])
assert car.stops == [3, 8], f"stops {car.stops}"
def test_keeps_going_up_past_a_nearer_call_behind():
"""rules 2 and 3: on the way up to 6, floors 2 and 8 call; 8 is ahead, so 2 waits until the car turns"""
car = run([(0, 6), (3, 2), (3, 8)])
assert car.stops == [6, 8, 2], f"stops {car.stops}"
def test_keeps_going_down_past_a_nearer_call_above():
"""rules 2 and 3 on the way down: floor 5 calls just behind the car, which goes on down to 1 first"""
car = run([(0, 1), (1, 5)], start=5)
assert car.stops == [1, 5], f"stops {car.stops}"
def test_the_morning_of_the_lesson():
"""18 floors, and nobody waits more than 21 ticks"""
car = run([(0, 9), (2, 2), (3, 7), (5, 2), (8, 3), (11, 0), (13, 4), (15, 3), (17, 1)])
assert car.stops == [2, 7, 9, 4, 3, 2, 1, 0], f"stops {car.stops}"
assert car.moved == 18, f"floors travelled {car.moved}"
assert max(car.waits) == 21, f"longest wait {max(car.waits)}" JavaScript · Starter code · sweep.mjs
// The lift's next-stop Strategy. The car (in the sample tests) calls nextStop(car) on every tick while it moves.
/** Keep going while anyone waits ahead or on this floor; then turn around. A car that has not moved yet goes to
* the nearest call. */
export class Sweep {
nextStop(car) {
// car.floor: where the car is; car.direction: +1 up, -1 down, 0 before the first move
// car.waiting: the calls not served yet, each with .floor and .at (the tick of the call)
if (car.waiting.length === 0) return null;
return car.waiting[0].floor; // the first call in the list: replace this with the three rules
}
} The sample tests · sweep.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { Sweep } from './sweep.mjs';
// ---- the lift car of the lesson, unchanged: its states decide, the scheduler only picks the next stop ----
class Idle {
step(car, now) {
return car.waiting.length ? new Moving().step(car, now) : this;
}
}
class Moving {
step(car, now) {
const stop = car.scheduler.nextStop(car);
if (stop === null || stop === undefined) return new Idle();
if (stop === car.floor) {
car.openDoors(now);
return new DoorsOpen();
}
car.direction = stop > car.floor ? 1 : -1;
car.floor += car.direction;
car.moved += 1;
return this;
}
}
class DoorsOpen {
step(car) {
return car.waiting.length ? new Moving() : new Idle();
}
}
class Car {
constructor(scheduler, floor = 0) {
Object.assign(this, { scheduler, state: new Idle(), floor, direction: 0, waiting: [], stops: [], waits: [], moved: 0 });
}
call(floor, at) {
this.waiting.push({ floor, at });
}
openDoors(now) {
const here = this.waiting.filter((c) => c.floor === this.floor);
this.waiting = this.waiting.filter((c) => c.floor !== this.floor);
this.waits.push(...here.map((c) => now - c.at));
this.stops.push(this.floor);
}
step(now) {
this.state = this.state.step(this, now);
}
}
function run(calls, start = 0, limit = 500) {
const car = new Car(new Sweep(), start);
const script = [...calls].sort((a, b) => a[0] - b[0] || a[1] - b[1]);
let now = 0;
while (now < limit && (script.length || car.waiting.length || !(car.state instanceof Idle))) {
while (script.length && script[0][0] === now) car.call(script.shift()[1], now);
car.step(now);
now += 1;
}
return car;
}
// ---- seven scripts of [tick, floor] calls ----
test('a new car goes to the nearest call', () => {
assert.deepEqual(run([[0, 7], [0, 2]], 5).stops, [7, 2]);
});
test('a tie goes to the call made first', () => {
assert.deepEqual(run([[0, 5], [1, 3], [2, 7]], 5).stops, [5, 3, 7]);
});
test('doors open at once for a call on the car floor', () => {
const car = run([[0, 4], [0, 1]], 4);
assert.deepEqual(car.stops, [4, 1]);
assert.equal(car.waits[0], 0);
});
test('stops for a call on a floor it is passing', () => {
assert.deepEqual(run([[0, 8], [3, 3]]).stops, [3, 8]);
});
test('keeps going up past a nearer call behind', () => {
assert.deepEqual(run([[0, 6], [3, 2], [3, 8]]).stops, [6, 8, 2]);
});
test('keeps going down past a nearer call above', () => {
assert.deepEqual(run([[0, 1], [1, 5]], 5).stops, [1, 5]);
});
test('the morning of the lesson', () => {
const car = run([[0, 9], [2, 2], [3, 7], [5, 2], [8, 3], [11, 0], [13, 4], [15, 3], [17, 1]]);
assert.deepEqual(car.stops, [2, 7, 9, 4, 3, 2, 1, 0]);
assert.equal(car.moved, 18);
assert.equal(Math.max(...car.waits), 21);
}); A hint
Start with rule 1: a car whose direction is 0 picks exactly what NearestFirst picks in the lesson, so its key (distance, then the tick of the call) works here too. For a moving car, sort the calls into two groups first: a call is ahead when call.floor - car.floor is zero or has the same sign as car.direction. Only when that group is empty do you look at the others.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
10 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
Fresher · How do you decide which design pattern to use for a requirement?
Start from the requirement, not from the catalogue. Find what varies, or what must be protected: an algorithm, an object’s own condition, the objects that react to a change, which class gets created, an interface you cannot change, or a dependency that fails. That variation points to a few candidates, such as Strategy for an algorithm picked from outside or State for behaviour that depends on the object’s condition. Then compare the candidates’ costs under your constraints: Decorator makes order matter, Observer makes you handle failing and forgotten subscribers, and State adds a class per state. If nothing varies, use no pattern. Finally, say the choice and the alternative you rejected, with the reason, in two sentences.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to senior
A parking lot charges by the hour on weekdays, a flat fee at weekends, and an extra fee at spots with an electric-vehicle charger. Which patterns would you use for pricing, and which alternative would you reject?
-
Mid to senior
In a design review you proposed Observer for order-status notifications. A colleague says a direct call from the order service to the notification service is simpler. How do you decide who is right?
Key takeaways
- Start from what varies, or must be protected, in the requirement; the pattern comes last.
- Four questions narrow the catalogue: which object to create, how objects fit or wrap, who decides and how objects talk, and how the code survives failure.
- Eliminate candidates by their consequences under your constraints: every pattern’s cost is part of the pattern.
- A requirement with no variation needs no pattern, and “there is only one” is not a reason for Singleton.
- In the lift, State handles the car’s own condition and Strategy the scheduling policy: FirstCome travelled 35 floors and NearestFirst 17, but kept one caller waiting 33 ticks, with no change to the car.
- State a choice in two sentences: the variation, the pattern, the cost, and the alternative rejected with its reason.
References
- Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley (Pearson))
- Python 3.14 documentation: Sorting Techniques (key functions) (Python Software Foundation)
- Python 3.14 documentation: dataclasses (Python Software Foundation)
- Site Reliability Engineering: Addressing Cascading Failures (Google)
- Circuit Breaker pattern (Microsoft (Azure Architecture Center))
- Yagni (Martin Fowler)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress