Low-Level Design (Object-Oriented Design) Module 1 – LLD rounds and a repeatable answer method
The machine-coding round playbook
Plan a 90-minute machine-coding round for low-level design (LLD): a model, an in-memory repository, a command dispatcher, tests, a demo and what to skip.
What you will learn
- Plan a 90-minute build: skeleton, in-memory storage, driver, tests and demo
- Structure code so that a new requirement touches one place
- Decide what to skip when time runs out, and say so explicitly
Before you start
On this page
A machine-coding round asks for working, readable code in a fixed time, often 90 to 120 minutes, in your own editor. You get a written problem statement, often with a sample input and the exact output it must produce. When the time is up you run it, walk the interviewer through the code, and are usually asked for a small change. Formats and lengths differ between employers; this lesson describes the format in general terms, not any company’s process.
Unlike a design discussion, the program is the product. A design that is not finished usually scores less than a smaller design that runs, so the plan below gets something running early and keeps it running.
A plan for 90 minutes
The minutes are MySmartCoPilot’s recommendation; shift them to your own speed, but keep the order:
- Model and interfaces, about 10 minutes. Read the statement twice, list the entities, and write the class skeletons and the public methods of the service. This is the answer framework squeezed into one step; the API skeleton is your design document.
- The happy path end to end, about 30 minutes. The main commands work on the sample input, from reading the input to printing the output. Nothing clever yet.
- Validation and errors, about 15 minutes. Unknown commands, wrong arguments, a full lot, a duplicate, an unknown id: one clear answer each, and the program carries on.
- Tests, about 15 minutes. Unit tests for the main rules, and one golden-output test: the sample input must produce exactly the sample output.
- An extension seam and clean-up, about 10 minutes. Rename what is unclear, remove duplication, and check that the most likely follow-up would land in one place.
- The demo, in the last 10 minutes. Run the sample input and the tests, then say what you skipped.
Structure the code so a change touches one place
The statement in this lesson is a classic: a parking lot with slots for bikes, cars and trucks; PARK <plate> <type> gives the lowest free slot of that type and a ticket; LEAVE <ticket> frees the slot; FREE <type> counts
free slots; STATUS lists the parked vehicles. A clean solution has four layers, and each kind of follow-up has a
home:
Four layers, and where each kind of change goes
Text description of the diagram
The diagram shows five boxes from top to bottom.
- Command lines in, answer lines out: the program reads lines such as PARK KA01AB1234 CAR and prints answers such as PARKED KA01AB1234 slot 3 ticket T1. A two-way arrow joins them to the dispatcher.
- dispatcher.py parses a line, checks the arguments, calls one handler and formats the answer. A new command is one new handler here.
- service.py, the ParkingLot service, holds the rules about slots, duplicate plates and tickets. The dispatcher passes it values and gets values back. A new rule changes this file.
- repository.py defines the TicketRepository interface (add, remove, find), which the service uses.
- InMemoryTicketRepository keeps the tickets in two dictionaries and implements the interface, shown by a dashed arrow. A database would be another class implementing the same interface.
Here is the whole solution, run on the sample input. The main file wires the parts together; the other tabs hold the dispatcher, the service, the repository and the value types:
parking/main.py
"""Reads commands from standard input and prints one answer per command."""
import sys
from dispatcher import run
from models import VehicleType
from repository import InMemoryTicketRepository
from service import ParkingLot
LAYOUT = {VehicleType.BIKE: 2, VehicleType.CAR: 2, VehicleType.TRUCK: 1}
def main():
lot = ParkingLot(LAYOUT, InMemoryTicketRepository())
for answer in run(sys.stdin, lot):
print(answer)
if __name__ == "__main__":
main() Input (standard input)
PARK KA01AB1234 CAR PARK MH12XY0001 car PARK DL3CAF0007 CAR PARK KA01AB1234 BIKE PARK KA05MN4821 BIKE FREE CAR LEAVE T1 LEAVE T1 PARK DL3CAF0007 CAR PARK TN09ZZ4321 BUS PARK FLY KA01AB1234 STATUS
Output
PARKED KA01AB1234 slot 3 ticket T1 PARKED MH12XY0001 slot 4 ticket T2 ERROR no free CAR slot ERROR KA01AB1234 is already parked PARKED KA05MN4821 slot 1 ticket T3 FREE CAR 0 LEFT KA01AB1234 slot 3 ERROR unknown ticket T1 PARKED DL3CAF0007 slot 3 ticket T4 ERROR unknown vehicle type BUS ERROR usage: PARK <plate> <type> ERROR unknown command FLY 1 KA05MN4821 BIKE 3 DL3CAF0007 CAR 4 MH12XY0001 CAR
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 main.py
parking/dispatcher.py
"""Turns command lines into calls on the ParkingLot, and its answers into output lines."""
import shlex
from models import VehicleType
from service import ParkingError
COMMANDS = {}
def command(name, *params):
"""Register the decorated function as the handler of NAME with these parameters."""
def register(handler):
COMMANDS[name] = (params, handler)
return handler
return register
def vehicle_type(text):
try:
return VehicleType(text.upper())
except ValueError:
raise ParkingError(f"unknown vehicle type {text}") from None
@command("PARK", "plate", "type")
def park(lot, plate, type_name):
ticket = lot.park(plate, vehicle_type(type_name))
return f"PARKED {plate} slot {ticket.slot} ticket {ticket.ticket_id}"
@command("LEAVE", "ticket")
def leave(lot, ticket_id):
ticket = lot.leave(ticket_id)
return f"LEFT {ticket.vehicle.plate} slot {ticket.slot}"
@command("FREE", "type")
def free(lot, type_name):
kind = vehicle_type(type_name)
return f"FREE {kind.value} {lot.free_slots(kind)}"
@command("STATUS")
def status(lot):
rows = [f"{t.slot} {t.vehicle.plate} {t.vehicle.type.value}" for t in lot.parked()]
return "\n".join(rows) or "EMPTY"
def run(lines, lot):
"""Yield one answer per command line. Bad input is answered with ERROR, never raised."""
for line in lines:
try:
words = shlex.split(line)
except ValueError as unreadable: # an unclosed quote, for example
yield f"ERROR {str(unreadable).lower()}"
continue
if not words:
continue
name, args = words[0].upper(), words[1:]
if name not in COMMANDS:
yield f"ERROR unknown command {words[0]}"
continue
params, handler = COMMANDS[name]
if len(args) != len(params):
yield "ERROR usage: " + " ".join([name, *(f"<{p}>" for p in params)])
continue
try:
yield handler(lot, *args)
except ParkingError as refusal:
yield f"ERROR {refusal}" parking/service.py
"""The rules of the lot: no parsing and no printing. Values in, values or a ParkingError out."""
from bisect import insort
from itertools import count
from models import Ticket, Vehicle
class ParkingError(Exception):
"""A request that the lot refuses; the message is meant for the user."""
class ParkingLot:
def __init__(self, layout, tickets):
"""layout: {VehicleType: number of slots}, numbered from 1 in that order.
tickets: a TicketRepository.
"""
self._tickets = tickets
self._free = {}
first = 1
for vehicle_type, size in layout.items():
self._free[vehicle_type] = list(range(first, first + size))
first += size
self._ids = count(1)
def park(self, plate, vehicle_type):
if self._tickets.find_by_plate(plate) is not None:
raise ParkingError(f"{plate} is already parked")
free = self._free.get(vehicle_type)
if not free:
raise ParkingError(f"no free {vehicle_type.value} slot")
ticket = Ticket(f"T{next(self._ids)}", Vehicle(plate, vehicle_type), free.pop(0))
self._tickets.add(ticket)
return ticket
def leave(self, ticket_id):
ticket = self._tickets.remove(ticket_id)
if ticket is None:
raise ParkingError(f"unknown ticket {ticket_id}")
insort(self._free[ticket.vehicle.type], ticket.slot)
return ticket
def free_slots(self, vehicle_type):
return len(self._free.get(vehicle_type, []))
def parked(self):
return self._tickets.all() parking/repository.py
"""Where tickets are kept. The service sees only the interface, so storage can change."""
from typing import Protocol
from models import Ticket
class TicketRepository(Protocol):
def add(self, ticket: Ticket) -> None: ...
def remove(self, ticket_id: str) -> Ticket | None: ...
def find_by_plate(self, plate: str) -> Ticket | None: ...
def all(self) -> list[Ticket]: ...
class InMemoryTicketRepository:
def __init__(self):
self._by_id = {}
self._by_plate = {}
def add(self, ticket):
self._by_id[ticket.ticket_id] = ticket
self._by_plate[ticket.vehicle.plate] = ticket
def remove(self, ticket_id):
ticket = self._by_id.pop(ticket_id, None)
if ticket is not None:
del self._by_plate[ticket.vehicle.plate]
return ticket
def find_by_plate(self, plate):
return self._by_plate.get(plate)
def all(self):
return sorted(self._by_id.values(), key=lambda t: t.slot) parking/models.py
"""The domain's value types: written in the first ten minutes, rarely changed afterwards."""
from dataclasses import dataclass
from enum import Enum
class VehicleType(Enum):
BIKE = "BIKE"
CAR = "CAR"
TRUCK = "TRUCK"
@dataclass(frozen=True)
class Vehicle:
plate: str
type: VehicleType
@dataclass(frozen=True)
class Ticket:
ticket_id: str
vehicle: Vehicle
slot: int 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
What to notice in each layer:
dispatcher.pyis the only code that knows about text. It splits a line withshlex.split(), which follows shell rules, so a quoted argument such as"KA 05 MN 4821"stays one word; a line it cannot split, such as one with an unclosed quote, gets anERRORline of its own. It looks the command up in a table, checks the number of arguments and turns everyParkingErrorinto anERRORline, so a bad line never stops the program. Each handler is registered by the@commanddecorator, so a new command is one new function.service.pyholds the rules and nothing else. It takes values (a plate, aVehicleType) and returns values (aTicket); it never parses or prints, so tests and other front ends can call it directly. It keeps a sorted list of free slots per vehicle type, so the lowest free slot is simply the first one in that list.repository.pyhides the storage. The service depends on theTicketRepositoryinterface, atyping.Protocol; the in-memory version uses two dictionaries, one by ticket id and one by plate. A database or a file would be another class with the same four methods, and the service would not change.models.pyholds frozen value types written in the first ten minutes:VehicleType,VehicleandTicket.
In-memory storage is the usual choice for a machine-coding round. Putting it behind an interface costs a few lines and answers the common follow-up “what if this had to be saved?” before it is asked.
Tests: the rules and one golden output
Two kinds of test pay off in a timed round. Unit tests pin down the rules that are easy to break while you refactor (here also the dispatcher’s answer to a line it cannot split), and a golden-output test runs the sample input and compares the answer, character for character, with the expected output:
import sys
import unittest
from dispatcher import run
from main import LAYOUT
from models import VehicleType
from repository import InMemoryTicketRepository
from service import ParkingError, ParkingLot
SAMPLE_OUTPUT = """\
PARKED KA01AB1234 slot 3 ticket T1
PARKED MH12XY0001 slot 4 ticket T2
ERROR no free CAR slot
ERROR KA01AB1234 is already parked
PARKED KA05MN4821 slot 1 ticket T3
FREE CAR 0
LEFT KA01AB1234 slot 3
ERROR unknown ticket T1
PARKED DL3CAF0007 slot 3 ticket T4
ERROR unknown vehicle type BUS
ERROR usage: PARK <plate> <type>
ERROR unknown command FLY
1 KA05MN4821 BIKE
3 DL3CAF0007 CAR
4 MH12XY0001 CAR"""
def new_lot():
return ParkingLot(LAYOUT, InMemoryTicketRepository())
class LotRules(unittest.TestCase):
def test_lowest_free_slot_of_the_type(self):
"""a car gets the lowest free car slot, and a freed slot is used again first"""
lot = new_lot()
first = lot.park("A1", VehicleType.CAR)
lot.park("A2", VehicleType.CAR)
lot.leave(first.ticket_id)
self.assertEqual(lot.park("A3", VehicleType.CAR).slot, first.slot)
def test_full_and_duplicate_are_refused(self):
"""a full lot and a plate that is already parked are refused"""
lot = new_lot()
lot.park("T1", VehicleType.TRUCK)
with self.assertRaisesRegex(ParkingError, "no free TRUCK slot"):
lot.park("T2", VehicleType.TRUCK)
with self.assertRaisesRegex(ParkingError, "already parked"):
lot.park("T1", VehicleType.CAR)
class CommandLines(unittest.TestCase):
def test_a_line_it_cannot_split_is_an_error(self):
"""a line with an unclosed quote gets an ERROR, and the next line still runs"""
lines = ['PARK "KA 01 AB 1234 CAR', 'PARK "KA 01 AB 1234" CAR']
answers = list(run(lines, new_lot()))
self.assertEqual(answers, ["ERROR no closing quotation", "PARKED KA 01 AB 1234 slot 3 ticket T1"])
class SampleInput(unittest.TestCase):
def test_sample_input_gives_the_sample_output(self):
"""commands.txt gives exactly the expected output"""
with open("commands.txt", encoding="utf-8") as sample:
answers = list(run(sample, new_lot()))
self.assertEqual("\n".join(answers), SAMPLE_OUTPUT)
if __name__ == "__main__":
load = unittest.defaultTestLoader.loadTestsFromTestCase
suite = unittest.TestSuite([load(LotRules), load(CommandLines), load(SampleInput)])
unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite) Output
test_full_and_duplicate_are_refused (__main__.LotRules.test_full_and_duplicate_are_refused) a full lot and a plate that is already parked are refused ... ok test_lowest_free_slot_of_the_type (__main__.LotRules.test_lowest_free_slot_of_the_type) a car gets the lowest free car slot, and a freed slot is used again first ... ok test_a_line_it_cannot_split_is_an_error (__main__.CommandLines.test_a_line_it_cannot_split_is_an_error) a line with an unclosed quote gets an ERROR, and the next line still runs ... ok test_sample_input_gives_the_sample_output (__main__.SampleInput.test_sample_input_gives_the_sample_output) commands.txt gives exactly the expected output ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK
This output changes from run to run: unittest prints how long the tests took, which differs from run to run
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 test_parking.py
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
The golden test is the one to write first: it protects everything that already works while you add the rest. When it fails, put the expected and the actual output side by side and look for the first line that differs.
Text Diff Paste the expected output and your program's output to see the first line where they differ.A follow-up that lands in one place
A typical last-minute request is “add a command that tells me where a vehicle is”. With a dispatch table, the
change is one handler. This file adds WHERE <plate> without editing any of the files above:
"""A last-minute follow-up, "add WHERE <plate>": one new handler, no other file changes."""
from dispatcher import command, run
from models import VehicleType
from repository import InMemoryTicketRepository
from service import ParkingError, ParkingLot
@command("WHERE", "plate")
def where(lot, plate):
ticket = next((t for t in lot.parked() if t.vehicle.plate == plate), None)
if ticket is None:
raise ParkingError(f"{plate} is not parked") # the dispatcher prints it as ERROR
return f"{plate} is in slot {ticket.slot}"
lot = ParkingLot({VehicleType.CAR: 2}, InMemoryTicketRepository())
for answer in run(["PARK KA01AB1234 CAR", "WHERE KA01AB1234", "WHERE MH12XY0001"], lot):
print(answer) Output
PARKED KA01AB1234 slot 1 ticket T1 KA01AB1234 is in slot 1 ERROR MH12XY0001 is not parked
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 follow_up.py
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
A new rule, such as “a bike may take a free car slot when every bike slot is taken”, would change only
ParkingLot, which is given the layout of the slots; a new input format would change only the dispatcher. If a follow-up forces you to edit three layers, say which
boundary was in the wrong place; that is a design insight, not a failure.
The same dispatcher in JavaScript
The idea does not depend on Python. Here is a smaller version in JavaScript, with the lot, a Map from command name
to handler, and the same error handling; only the lot’s own ParkingError becomes an ERROR line, and anything else
is rethrown:
// The dispatcher idea in JavaScript: a table from command name to handler,
// and all parsing kept out of the lot.
class ParkingError extends Error {}
class ParkingLot {
constructor(carSlots) {
this.free = Array.from({ length: carSlots }, (_, i) => i + 1);
this.slotOf = new Map();
}
park(plate) {
if (this.slotOf.has(plate)) throw new ParkingError(`${plate} is already parked`);
if (this.free.length === 0) throw new ParkingError("no free CAR slot");
const slot = this.free.shift();
this.slotOf.set(plate, slot);
return slot;
}
leave(plate) {
const slot = this.slotOf.get(plate);
if (slot === undefined) throw new ParkingError(`${plate} is not parked`);
this.slotOf.delete(plate);
this.free.push(slot);
this.free.sort((a, b) => a - b);
return slot;
}
freeSlots() {
return this.free.length;
}
}
const COMMANDS = new Map([
["PARK", { params: ["plate"], handle: (lot, p) => `PARKED ${p} slot ${lot.park(p)}` }],
["LEAVE", { params: ["plate"], handle: (lot, p) => `LEFT ${p} slot ${lot.leave(p)}` }],
["FREE", { params: [], handle: (lot) => `FREE CAR ${lot.freeSlots()}` }],
]);
export function run(lines, lot) {
const answers = [];
for (const line of lines) {
const [word = "", ...args] = line.trim().split(/\s+/);
if (word === "") continue;
const name = word.toUpperCase();
const command = COMMANDS.get(name);
if (command === undefined) {
answers.push(`ERROR unknown command ${word}`);
} else if (args.length !== command.params.length) {
const usage = [name, ...command.params.map((p) => `<${p}>`)].join(" ");
answers.push(`ERROR usage: ${usage}`);
} else {
try {
answers.push(command.handle(lot, ...args));
} catch (error) {
if (!(error instanceof ParkingError)) throw error;
answers.push(`ERROR ${error.message}`);
}
}
}
return answers;
}
const sample = [
"PARK KA01AB1234",
"PARK MH12XY0001",
"PARK DL3CAF0007",
"LEAVE KA01AB1234",
"PARK DL3CAF0007",
"FREE",
"FLY away",
];
for (const answer of run(sample, new ParkingLot(2))) console.log(answer); Output
PARKED KA01AB1234 slot 1 PARKED MH12XY0001 slot 2 ERROR no free CAR slot LEFT KA01AB1234 slot 1 PARKED DL3CAF0007 slot 1 FREE CAR 0 ERROR unknown command FLY
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node dispatcher.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
Use the language you type fastest and whose standard library you know best, if the round lets you choose.
The same layout in other languages
Each language has a usual way to split a small program into files and run it on an input file. The commands below are the ones the languages’ own documentation gives; this page runs the Python and JavaScript versions, and a later module of this track writes one design in each of these languages.
- Python:
main.py,dispatcher.py,service.py,repository.pyandmodels.pyin one folder. Runpython3 main.py < commands.txt. - Java 22 or later:
Main.javaand one file per top-level class (ParkingLot.java,Dispatcher.java…) in one folder. Runjava Main.java < commands.txt; for each class the code refers to, the launcher finds the file of the same name (ParkingLot.javaforParkingLot) and compiles it in memory. - Go: a module with
go.mod,main.goin packagemain, and aparkingpackage in its own folder. Rungo run . < commands.txt. - Rust: a Cargo package with
Cargo.toml,src/main.rsdeclaringmod parking;, andsrc/parking.rs. Runcargo run < commands.txt.
What to skip when time runs out, and how to say it
You will rarely finish everything. Decide what to drop on purpose, and say it, instead of running out of time in the middle of something.
- Skip without regret: persistence (the in-memory repository is enough), concurrency (say where a lock would go), input formats beyond the statement, pretty output, configuration files, logging, and tests beyond the main rules and the golden test.
- Never skip: the exact output of the sample input, the rules the statement names, and the separation between parsing, rules and storage. Those are what the round is judged on.
- Say it explicitly. Keep a short “not done” list at the top of
main.pyor in a README, and read it out in the demo: “Not done: fees at exit. I would keep the entry time on theTicket, add a fee rule toParkingLotand print the fee in theLEAVEhandler.” A named gap with a plan reads as a decision; a silent gap reads as a mistake.
Key takeaways
- Work in this order: model and interfaces, the happy path end to end, errors, tests, a seam and clean-up, the demo.
- Keep parsing in a dispatcher, rules in a service and storage behind a repository interface; each kind of change then has one home.
- A dispatch table makes a new command one handler; an in-memory repository behind an interface answers the storage question for free.
- Write the golden-output test for the sample input first.
- Skip on purpose, keep a “not done” list, and say it in the demo.
Exercise
Exercise · Medium · Python
Write the command dispatcher for a meeting-room service
room_commands.py contains a finished in-memory service, RoomBook, for booking meeting rooms by the hour. Do not change it. Write run(lines), the dispatcher: it creates a new RoomBook, answers each command line with exactly one output line, and returns the list of answers. Blank lines are skipped, and command names may be written in any case (book is BOOK). The five commands:
ADD_ROOM <name> <seats>answersADDED <name>.BOOK <room> <hour> <person>answersBOOKED <id>, with the id the service returns (B1,B2…).CANCEL <id>answersCANCELLED <id>.FREE <hour> <seats>answersFREEfollowed by the names of the rooms that are free at that hour and have at least that many seats, separated by spaces, orFREE none.SCHEDULE <room>answersSCHEDULE <room>followed by its bookings as<hour>:<person>, in hour order, orSCHEDULE <room> empty.
When a line cannot be carried out, the answer starts with ERROR and the program carries on:
- an unknown command:
ERROR unknown command <the word as typed>; - the wrong number of arguments:
ERROR usage:and the command with its parameters, for exampleERROR usage: BOOK <room> <hour> <person>(the parameter names are the ones in the list above); - an hour or a number of seats that is not a whole number:
ERROR hour must be a whole numberorERROR seats must be a whole number; - a refusal of the service (a
BookingError):ERROR <its message>, for exampleERROR no room Kaveri.
Keep the parsing in run() and its helpers, and leave every rule about rooms and bookings to the service. The sample tests run command scripts and compare the answers exactly.
Starter code · room_commands.py
"""Meeting rooms. The service RoomBook is given; write run(), the command dispatcher."""
class BookingError(Exception):
"""A request that the rules refuse; the message is meant for the user."""
class RoomBook:
"""Given: an in-memory meeting-room service. Do not change it."""
OPEN, CLOSE = 8, 18 # a booking starts on the hour, at 8, 9 … 17, and lasts one hour
def __init__(self):
self._rooms = {} # name -> seats
self._bookings = {} # booking id -> (room, hour, person)
self._next_id = 1
def add_room(self, name, seats):
if name in self._rooms:
raise BookingError(f"room {name} exists")
if seats < 1:
raise BookingError("a room has at least 1 seat")
self._rooms[name] = seats
def book(self, room, hour, person):
if room not in self._rooms:
raise BookingError(f"no room {room}")
if not self.OPEN <= hour < self.CLOSE:
raise BookingError(f"rooms open from {self.OPEN} to {self.CLOSE}")
if any(r == room and h == hour for r, h, _ in self._bookings.values()):
raise BookingError(f"{room} is booked at {hour}")
booking_id = f"B{self._next_id}"
self._next_id += 1
self._bookings[booking_id] = (room, hour, person)
return booking_id
def cancel(self, booking_id):
if self._bookings.pop(booking_id, None) is None:
raise BookingError(f"no booking {booking_id}")
def free_rooms(self, hour, seats):
taken = {r for r, h, _ in self._bookings.values() if h == hour}
fits = [name for name, size in self._rooms.items() if size >= seats]
return sorted(name for name in fits if name not in taken)
def schedule(self, room):
if room not in self._rooms:
raise BookingError(f"no room {room}")
return sorted((h, p) for r, h, p in self._bookings.values() if r == room)
def run(lines):
"""Answer each command line with one output line, on a new RoomBook (see the prompt)."""
service = RoomBook()
answers = []
# Write the dispatcher here.
return answers The sample tests · test_room_commands.py
from room_commands import run
def test_happy_path():
"""each command gives its answer"""
script = [
"ADD_ROOM Indus 6",
"ADD_ROOM Ganga 10",
"BOOK Indus 10 asha",
"FREE 10 4",
"SCHEDULE Indus",
"CANCEL B1",
"SCHEDULE Indus",
]
assert run(script) == [
"ADDED Indus",
"ADDED Ganga",
"BOOKED B1",
"FREE Ganga",
"SCHEDULE Indus 10:asha",
"CANCELLED B1",
"SCHEDULE Indus empty",
]
def test_schedule_and_free_lists():
"""SCHEDULE lists bookings in hour order; FREE lists rooms by name, or none"""
script = [
"ADD_ROOM Indus 6",
"ADD_ROOM Ganga 10",
"BOOK Indus 15 ravi",
"BOOK Indus 9 mei",
"SCHEDULE Indus",
"FREE 11 4",
"FREE 9 50",
]
assert run(script)[-3:] == ["SCHEDULE Indus 9:mei 15:ravi", "FREE Ganga Indus", "FREE none"]
def test_service_refusals():
"""the service's refusals are answered as ERROR <message>"""
script = [
"ADD_ROOM Indus 6",
"BOOK Indus 10 asha",
"BOOK Indus 10 ravi",
"BOOK Kaveri 9 ravi",
"BOOK Indus 18 ravi",
"CANCEL B9",
"ADD_ROOM Indus 4",
]
assert run(script)[2:] == [
"ERROR Indus is booked at 10",
"ERROR no room Kaveri",
"ERROR rooms open from 8 to 18",
"ERROR no booking B9",
"ERROR room Indus exists",
]
def test_unknown_command():
"""an unknown command is named in the error, as it was typed"""
assert run(["RESERVE Indus 10"]) == ["ERROR unknown command RESERVE"]
def test_wrong_number_of_arguments():
"""a wrong number of arguments gives the usage of the command"""
assert run(["BOOK Indus 10", "FREE", "CANCEL B1 B2"]) == [
"ERROR usage: BOOK <room> <hour> <person>",
"ERROR usage: FREE <hour> <seats>",
"ERROR usage: CANCEL <id>",
]
def test_numbers_are_checked():
"""an hour or a number of seats that is not a whole number is refused"""
script = ["ADD_ROOM Kaveri lots", "ADD_ROOM Kaveri 4", "BOOK Kaveri ten asha", "FREE 9 four"]
assert run(script) == [
"ERROR seats must be a whole number",
"ADDED Kaveri",
"ERROR hour must be a whole number",
"ERROR seats must be a whole number",
]
def test_case_and_blank_lines():
"""command names may be in any case, and blank lines are skipped"""
assert run(["add_room Indus 4", "", " ", "Book Indus 9 mei"]) == ["ADDED Indus", "BOOKED B1"]
def test_each_run_starts_empty():
"""every call of run() starts with no rooms and no bookings"""
run(["ADD_ROOM Indus 4"])
assert run(["SCHEDULE Indus"]) == ["ERROR no room Indus"] A hint
Write one small function per command that takes the service and the argument strings and returns the answer line, and a dictionary from each command name to its parameter names and its function. run() then only splits the line, looks the name up, compares the number of arguments with the parameter names, and catches BookingError. A helper that turns "10" into 10 and raises BookingError("hour must be a whole number") for "ten" keeps the handlers short.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 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
6 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.
References
- shlex: Simple lexical analysis (Python Software Foundation)
- unittest: Unit testing framework (Python Software Foundation)
- typing: Protocol (Python Software Foundation)
- JEP 458: Launch Multi-File Source-Code Programs (OpenJDK)
- Command go: Compile and run Go program (The Go Authors)
- cargo run (The Cargo Book) (The Rust Project)
- Separating Modules into Different Files (The Rust Programming Language) (The Rust Project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress