Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Low-Level Design (Object-Oriented Design) Module 1 – LLD rounds and a repeatable answer method

Clarifying requirements and scope

Turn a vague low-level design prompt into numbered, testable requirements and an out-of-scope list by asking about rules, then trace each rule to a unit test.

  • Beginner
  • 25 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7 and Node.js 24.21.0
  • By MySmartCoPilot

What you will learn

  • Ask questions that separate functional from non-functional requirements
  • Write numbered, testable requirements with explicit out-of-scope items
  • Convert each requirement into at least one acceptance test case

Before you start

On this page

Low-level design prompts are short on purpose: “design a parking lot”, “design a library”. The rules that make a design right or wrong are missing. Who pays how much? What happens on a tie, after midnight, when something is missing or done twice? Until those rules are known, no design can be checked, and an interviewer may hold some back to see whether you ask.

Clarifying is the first step of the answer framework. Its output is small and concrete: a short list of numbered, testable requirements and an equally explicit list of what is out of scope, in about five minutes.

Ask about behaviour, not technology

Useful questions are about what the system does and the rules it follows. A checklist to run through:

  • Actors and actions. Who uses it, and what can each of them do? (Driver, attendant, administrator.)
  • Rules and limits. How many, how much, how long, and exactly where the boundary is: is 60 minutes one hour or two?
  • Failures. What if the thing is missing, full, already done, expired or paid twice?
  • Money and time. Units and rounding, time zones, stays that cross midnight, periods that start or end.
  • Lifecycle. Which states does an object go through, can a step be undone, does anything expire?
  • Qualities inside the program. May two threads use it at once? How much work may one operation do? Must the results be repeatable in tests?

Questions about databases, clouds or services belong to a high-level design round. In an LLD round they use up time and say nothing about the classes.

Functional and non-functional requirements

A functional requirement says what the system does: “a car pays 40 for each started hour”. A non-functional requirement says how well, or under what constraints, it does it. In low-level design these qualities are local to one program:

  • Determinism: the fee calculator takes the time as an argument instead of reading the clock, so a test can choose any moment.
  • Cost of an operation: pricing a stay takes the same few steps whatever its length, with no loop over hours.
  • Thread safety: two exits paying the same ticket at the same moment cannot both succeed.
  • Memory bounds: a rate limiter keeps a fixed amount of data per user, not every request it has seen.

Good requirement guides keep both kinds and ask that each one can be verified. NASA’s checklist for requirements, for example, asks whether each requirement is uniquely numbered, whether it states what is needed rather than how to build it, and whether a test, demonstration, inspection or analysis can show that it is met. It lists words such as “fast”, “user-friendly” and “flexible” as ones to avoid (NASA Systems Engineering Handbook, Appendix C).

A worked clarification: parking fees

Prompt: “Design the fee calculation for a parking lot.” Here is a five-minute exchange, condensed:

  1. Which vehicles? Cars and motorcycles. A ticket at the entry records the type and the time.
  2. How is the fee counted? Per started hour: car 40, motorcycle 20. One minute into an hour counts as the hour.
  3. Is a short stop free? Up to 15 minutes, yes. A longer stay is charged from its first minute.
  4. Is there a maximum? Yes: each started 24-hour block costs at most 250 for a car and 120 for a motorcycle.
  5. What if a stay crosses midnight? Nothing special; the blocks are counted from the entry time.
  6. A lost ticket? A flat 500.
  7. What can go wrong at the pay desk? An unknown ticket, a ticket that is already paid, an exit time before the entry time. Each is refused with its own message.
  8. Payments by card, monthly passes, several lots? Not now.

The result is written down at once, numbered, one rule per line:

  • R1 A ticket records the vehicle type (car or motorcycle) and the entry time.
  • R2 Each started hour costs 40 for a car and 20 for a motorcycle.
  • R3 A stay of 15 minutes or less costs nothing; a longer stay is charged from its first minute.
  • R4 Each started 24-hour block, counted from the entry time, costs at most 250 for a car and 120 for a motorcycle.
  • R5 A stay that crosses midnight is one stay; nothing restarts at midnight.
  • R6 A lost ticket costs a flat 500, whatever the stay.
  • R7 These are refused, each with its own message: an unknown ticket, a second payment of a ticket, an exit time before the entry time.
  • R8 (non-functional) The calculator never reads the clock; the times are passed in.
  • R9 (non-functional) Pricing a stay takes the same few steps whatever its length.
  • Out of scope: O1 how the money is collected (cash, cards, wallets); O2 monthly passes and reservations; O3 several lots and choosing a parking spot; O4 storage and hardware (barriers, cameras, ticket printers): everything stays in memory.

Out of scope is a requirement too. It stops you from building what nobody asked for (gold-plating), and it shows the interviewer that you saw those areas and left them out on purpose.

From requirements to tests

Here is a fee calculator and a pay desk written from R1 to R9. The comments name the requirement each constant or check comes from:

Parking fees from requirements R1 to R9 Python · fees/parking_fee.py
"""Parking fees, written from the lesson's requirements R1 to R9."""
from dataclasses import dataclass
from datetime import datetime, timedelta

RATE = {"car": 40, "motorcycle": 20}  # R2: per started hour
DAILY_CAP = {"car": 250, "motorcycle": 120}  # R4: per started 24-hour block
GRACE = timedelta(minutes=15)  # R3
LOST_TICKET_FEE = 500  # R6
HOUR, DAY = timedelta(hours=1), timedelta(hours=24)


class FeeError(Exception):
    """A payment that the rules refuse (R7)."""


def started_hours(duration):
    whole, rest = divmod(duration, HOUR)
    return whole + (1 if rest else 0)


def fee(kind, entered, left):
    """The fee of one stay: the exit time is passed in (R8), and any stay costs the same work (R9)."""
    if left < entered:
        raise FeeError("exit before entry")
    stay = left - entered
    if stay <= GRACE:
        return 0
    full_days, rest = divmod(stay, DAY)
    last_block = min(started_hours(rest) * RATE[kind], DAILY_CAP[kind])
    return full_days * DAILY_CAP[kind] + last_block


@dataclass
class Ticket:
    number: int
    kind: str
    entered: datetime
    paid: bool = False


class PayDesk:
    def __init__(self):
        self.tickets = {}

    def issue(self, kind, entered):
        if kind not in RATE:
            raise FeeError(f"no rate for {kind}")
        ticket = Ticket(len(self.tickets) + 1, kind, entered)  # R1
        self.tickets[ticket.number] = ticket
        return ticket

    def pay(self, number, left, lost=False):
        ticket = self.tickets.get(number)
        if ticket is None:
            raise FeeError("unknown ticket")
        if ticket.paid:
            raise FeeError("already paid")
        amount = LOST_TICKET_FEE if lost else fee(ticket.kind, ticket.entered, left)
        ticket.paid = True
        return amount


if __name__ == "__main__":
    nine = datetime(2026, 1, 5, 9, 0)
    stays = [("car", 15), ("car", 16), ("car", 61), ("car", 7 * 60), ("car", 30 * 60),
             ("motorcycle", 125)]
    for kind, minutes in stays:
        amount = fee(kind, nine, nine + timedelta(minutes=minutes))
        print(f"{kind:<10} {minutes:>5} min  fee {amount}")
    late = nine.replace(hour=23, minute=30)
    amount = fee("car", late, late + timedelta(minutes=100))
    print(f"car 23:30 to 01:10 next day  fee {amount}")

    desk = PayDesk()
    ticket = desk.issue("car", nine)
    paid = desk.pay(ticket.number, nine + timedelta(minutes=80))
    print("ticket", ticket.number, "pays", paid)
    for number in (ticket.number, 7):
        try:
            desk.pay(number, nine + timedelta(hours=2))
        except FeeError as refusal:
            print("ticket", number, "refused:", refusal)

Output

car           15 min  fee 0
car           16 min  fee 40
car           61 min  fee 80
car          420 min  fee 250
car         1800 min  fee 490
motorcycle   125 min  fee 60
car 23:30 to 01:10 next day  fee 80
ticket 1 pays 80
ticket 1 refused: already paid
ticket 7 refused: unknown ticket

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 parking_fee.py

Each requirement then becomes at least one test whose name starts with its number. A reviewer, or an interviewer, can read down the list and see that nothing was forgotten:

One test per requirement, named after it Python · fees/test_parking_fee.py
import sys
import time
import unittest
from datetime import datetime, timedelta

from parking_fee import FeeError, PayDesk, fee

NINE = datetime(2026, 1, 5, 9, 0)


def after(**duration):
    return NINE + timedelta(**duration)


class Fees(unittest.TestCase):
    def test_r1_ticket(self):
        """R1: a ticket records the vehicle type and the entry time"""
        ticket = PayDesk().issue("motorcycle", NINE)
        self.assertEqual((ticket.kind, ticket.entered), ("motorcycle", NINE))

    def test_r2_started_hours(self):
        """R2: 61 minutes are two started hours"""
        self.assertEqual(fee("car", NINE, after(minutes=61)), 80)
        self.assertEqual(fee("motorcycle", NINE, after(minutes=61)), 40)

    def test_r3_grace_period(self):
        """R3: 15 minutes cost nothing, 16 minutes cost one hour"""
        self.assertEqual(fee("car", NINE, after(minutes=15)), 0)
        self.assertEqual(fee("car", NINE, after(minutes=16)), 40)

    def test_r4_daily_cap(self):
        """R4: 7 hours are capped at 250; 30 hours cost 250 + 6 x 40"""
        self.assertEqual(fee("car", NINE, after(hours=7)), 250)
        self.assertEqual(fee("car", NINE, after(hours=30)), 490)

    def test_r5_midnight(self):
        """R5: 23:30 to 01:10 is one stay of two started hours"""
        late = NINE.replace(hour=23, minute=30)
        self.assertEqual(fee("car", late, late + timedelta(minutes=100)), 80)

    def test_r6_lost_ticket(self):
        """R6: a lost ticket costs 500, whatever the stay"""
        desk = PayDesk()
        ticket = desk.issue("car", NINE)
        self.assertEqual(desk.pay(ticket.number, after(minutes=20), lost=True), 500)

    def test_r7_refusals(self):
        """R7: an unknown ticket, a second payment and an exit before entry are refused"""
        desk = PayDesk()
        ticket = desk.issue("car", NINE)
        desk.pay(ticket.number, after(hours=1))
        cases = [
            ("unknown ticket", lambda: desk.pay(99, after(hours=1))),
            ("already paid", lambda: desk.pay(ticket.number, after(hours=2))),
            ("exit before entry", lambda: fee("car", NINE, after(minutes=-5))),
        ]
        for message, action in cases:
            with self.subTest(message):
                with self.assertRaises(FeeError) as refused:
                    action()
                self.assertEqual(str(refused.exception), message)

    def test_r8_time_passed_in(self):
        """R8: the times are passed in, so a test can use any moment"""
        far = datetime(2040, 6, 1, 9, 0)
        later = fee("car", far, far + timedelta(hours=3))
        self.assertEqual(later, fee("car", NINE, after(hours=3)))

    def test_r9_constant_work(self):
        """R9: a thousand ten-year stays are priced in well under a second"""
        start = time.perf_counter()
        for _ in range(1000):
            amount = fee("car", NINE, after(days=3650))
        self.assertEqual(amount, 3650 * 250)
        self.assertLess(time.perf_counter() - start, 1.0)


if __name__ == "__main__":
    suite = unittest.defaultTestLoader.loadTestsFromTestCase(Fees)
    unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite)

Output

test_r1_ticket (__main__.Fees.test_r1_ticket)
R1: a ticket records the vehicle type and the entry time ... ok
test_r2_started_hours (__main__.Fees.test_r2_started_hours)
R2: 61 minutes are two started hours ... ok
test_r3_grace_period (__main__.Fees.test_r3_grace_period)
R3: 15 minutes cost nothing, 16 minutes cost one hour ... ok
test_r4_daily_cap (__main__.Fees.test_r4_daily_cap)
R4: 7 hours are capped at 250; 30 hours cost 250 + 6 x 40 ... ok
test_r5_midnight (__main__.Fees.test_r5_midnight)
R5: 23:30 to 01:10 is one stay of two started hours ... ok
test_r6_lost_ticket (__main__.Fees.test_r6_lost_ticket)
R6: a lost ticket costs 500, whatever the stay ... ok
test_r7_refusals (__main__.Fees.test_r7_refusals)
R7: an unknown ticket, a second payment and an exit before entry are refused ... ok
test_r8_time_passed_in (__main__.Fees.test_r8_time_passed_in)
R8: the times are passed in, so a test can use any moment ... ok
test_r9_constant_work (__main__.Fees.test_r9_constant_work)
R9: a thousand ten-year stays are priced in well under a second ... ok

----------------------------------------------------------------------
Ran 9 tests in 0.002s

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_fee.py

unittest treats every method whose name starts with test as a test, and with verbosity=2 it prints each test’s name and the first line of its docstring. The last lines load the tests of Fees and run them with a runner that writes to standard output (by default unittest reports on standard error). Loading the class directly also works where unittest.main() cannot find the tests by itself, as in this page’s browser runner; on your computer, python3 -m unittest -v test_parking_fee runs the same tests.

What each test pins down:

  • test_r1_ticket: the ticket keeps the vehicle type and the entry time.
  • test_r2_started_hours: 61 minutes are two started hours, at both rates.
  • test_r3_grace_period: 15 minutes cost 0, and 16 minutes cost one hour.
  • test_r4_daily_cap: 7 hours pay the cap; 30 hours pay the cap once plus 6 started hours.
  • test_r5_midnight: 23:30 to 01:10 is priced as one stay of two started hours.
  • test_r6_lost_ticket: a lost ticket costs 500, even after only 20 minutes.
  • test_r7_refusals: each refusal raises FeeError with its own message, one sub-test each.
  • test_r8_time_passed_in: a three-hour stay costs the same on any date, even years ahead.
  • test_r9_constant_work: a thousand ten-year stays are priced in well under a second.

R9’s test has a generous margin on purpose: a calculation that loops over every hour would need millions of steps for those stays and fail it, while normal slowdowns of a busy computer or a browser would not.

Break one rule and watch its test fail

Save both files in one folder on your computer, change GRACE in parking_fee.py to 10 minutes and run python3 test_parking_fee.py. Only test_r3_grace_period fails, and its name tells you which requirement you broke.

Boundaries hide inside the rules

Most wrong answers are right in the middle and wrong at the edges. “Per started hour” is a typical trap: dividing the minutes by 60 with floor division rounds the hour down and undercharges every stay that is not a whole number of hours. The edge case is exactly 60 minutes, where both rules agree:

Rounding down is not the same as counting started hours Python · rounding/started_hours.py
from datetime import timedelta

HOUR = timedelta(hours=1)


def hours_rounded_down(stay):
    return stay // HOUR  # the common mistake: a started hour is dropped


def hours_started(stay):
    whole, rest = divmod(stay, HOUR)
    return whole + (1 if rest else 0)


for minutes in (59, 60, 61, 125):
    stay = timedelta(minutes=minutes)
    down, started = hours_rounded_down(stay), hours_started(stay)
    print(f"{minutes} min: rounded down {down}, started {started}")

Output

59 min: rounded down 0, started 1
60 min: rounded down 1, started 1
61 min: rounded down 1, started 2
125 min: rounded down 2, started 3

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 started_hours.py

timedelta // timedelta gives a whole number rounded down, and divmod() gives that number and the remainder, so “add one when there is a remainder” counts started hours exactly, with no floating-point division. Whenever a rule has a number in it, test just below, at and just above that number.

Rules that are not stated at all are where follow-up questions come from. Listen for:

  • Ties: two bids at the same price, two players with the same score.
  • Crossing a boundary: a booking that spans midnight, a stay that starts on one tariff and ends on another.
  • Failure halfway: what happens to a reserved seat if the payment fails afterwards?
  • Repetition: the same request twice, a second cancellation, a retry after a timeout.

Key takeaways

  • Ask about behaviour and rules (actors, limits, failures, money, time, lifecycle), not about technology.
  • Write requirements down as you hear them, numbered (R1, R2 …), one rule each, with numbers instead of words such as “fast”.
  • Non-functional requirements in LLD are local: deterministic time, the cost of one operation, thread safety and memory bounds.
  • List what is out of scope; it is a decision too.
  • Turn every requirement into at least one test named after it, and test both sides of every boundary.

Exercise

Exercise · Medium · Python

Turn a printer queue's rules into acceptance cases

The prompt: "Our floor shares one printer. People send jobs, the printer prints them in order, and people can cancel their own jobs. Big jobs clog it, so there should be a limit." You asked questions, and these are the answers:

  • How is a job sent? SUBMIT <job> <user> <pages>, and the printer answers QUEUED <job>.
  • In what order are jobs printed? In the order they were sent. PRINT prints the oldest waiting job and answers PRINTED <job>; with nothing waiting it answers IDLE.
  • What is a big job? More than 50 pages: REJECTED <job> too many pages. Exactly 50 pages is fine.
  • Can one person fill the queue? No: at most 2 waiting jobs per user. Another one gets REJECTED <job> too many jobs.
  • Who may cancel? Only the job's owner: CANCEL <job> <user> answers CANCELLED <job>. For someone else's job it answers REFUSED <job> not yours, and for a job that is not waiting, UNKNOWN <job>.
  • Can people see the queue? STATUS answers the waiting job ids, oldest first, separated by spaces, or EMPTY.

In printer_cases.py, write the answers as numbered requirements in REQUIREMENTS ("R1", "R2" …, at least six, each with a short text) and acceptance cases in CASES. A case is a dictionary with the requirement it checks, the command lines to run on a fresh printer, and the exact answer expected for each line. R1 and its case are done for you.

The sample tests check that every case names a requirement and every requirement has a case, run your cases on a correct printer (every case must pass) and on three printers with a bug each. Every buggy printer must fail at least one of your cases, so think about the boundaries and the people the rules are about.

Starter code · printer_cases.py

"""Numbered requirements and acceptance cases for the shared printer queue of the prompt."""

REQUIREMENTS = {
    "R1": "SUBMIT <job> <user> <pages> adds the job at the back and answers QUEUED <job>",
}

CASES = [
    {
        "requirement": "R1",
        "commands": ["SUBMIT a ana 3"],
        "expected": ["QUEUED a"],
    },
]
The sample tests · test_printer_cases.py
import re

from printer_cases import CASES, REQUIREMENTS


class Printer:
    """A correct shared printer queue, built from the answers in the prompt."""

    PAGE_LIMIT = 50
    JOBS_PER_USER = 2

    def __init__(self):
        self.queue = []  # (job, user), oldest first

    def submit(self, job, user, pages):
        if pages > self.PAGE_LIMIT:
            return f"REJECTED {job} too many pages"
        if sum(1 for _, owner in self.queue if owner == user) >= self.JOBS_PER_USER:
            return f"REJECTED {job} too many jobs"
        self.queue.append((job, user))
        return f"QUEUED {job}"

    def take_next(self):
        return self.queue.pop(0)

    def print_next(self):
        if not self.queue:
            return "IDLE"
        job, _ = self.take_next()
        return f"PRINTED {job}"

    def may_cancel(self, owner, user):
        return owner == user

    def cancel(self, job, user):
        for i, (waiting, owner) in enumerate(self.queue):
            if waiting == job:
                if not self.may_cancel(owner, user):
                    return f"REFUSED {job} not yours"
                del self.queue[i]
                return f"CANCELLED {job}"
        return f"UNKNOWN {job}"

    def status(self):
        return " ".join(job for job, _ in self.queue) or "EMPTY"


class NewestFirst(Printer):
    """Bug: prints the newest job first."""

    def take_next(self):
        return self.queue.pop()


class FiftyPagesRefused(Printer):
    """Bug: refuses a job of exactly 50 pages."""

    PAGE_LIMIT = 49


class AnyoneMayCancel(Printer):
    """Bug: lets anyone cancel any job."""

    def may_cancel(self, owner, user):
        return True


def run(printer, commands):
    """What the printer answers to each command line."""
    out = []
    for line in commands:
        name, *args = line.split()
        if name == "SUBMIT":
            job, user, pages = args
            out.append(printer.submit(job, user, int(pages)))
        elif name == "PRINT":
            out.append(printer.print_next())
        elif name == "CANCEL":
            out.append(printer.cancel(*args))
        elif name == "STATUS":
            out.append(printer.status())
        else:
            raise ValueError(f"unknown command {line!r}")
    return out


def caught(buggy_printer):
    """True when at least one case gives a different answer on the buggy printer."""
    return any(run(buggy_printer(), case["commands"]) != case["expected"] for case in CASES)


def test_requirements_are_numbered():
    """at least six requirements, numbered R1, R2 …, each with its text"""
    assert len(REQUIREMENTS) >= 6, f"only {len(REQUIREMENTS)} requirements"
    for rid, text in REQUIREMENTS.items():
        assert re.fullmatch(r"R[1-9][0-9]*", rid), f"{rid!r} is not numbered like R1"
        assert isinstance(text, str) and text.strip(), f"{rid} has no text"


def test_cases_name_their_requirement():
    """every case names one of the requirements and expects one line per command"""
    for i, case in enumerate(CASES, start=1):
        assert case["requirement"] in REQUIREMENTS, f"case {i} names {case['requirement']!r}"
        assert case["commands"], f"case {i} has no commands"
        assert len(case["expected"]) == len(case["commands"]), f"case {i}: one line per command"


def test_every_requirement_has_a_case():
    """every requirement is checked by at least one case"""
    covered = {case["requirement"] for case in CASES}
    assert sorted(set(REQUIREMENTS) - covered) == []


def test_cases_pass_on_the_correct_printer():
    """every case passes on a correct printer"""
    for i, case in enumerate(CASES, start=1):
        answers = run(Printer(), case["commands"])
        assert answers == case["expected"], f"case {i} ({case['requirement']})"


def test_catches_newest_first():
    """a case fails on a printer that prints the newest job first"""
    assert caught(NewestFirst)


def test_catches_fifty_pages_refused():
    """a case fails on a printer that refuses a job of exactly 50 pages"""
    assert caught(FiftyPagesRefused)


def test_catches_anyone_may_cancel():
    """a case fails on a printer that lets anyone cancel any job"""
    assert caught(AnyoneMayCancel)
A hint

A case catches a bug only if the buggy printer would answer differently. For the order of printing, send two jobs before the first PRINT; for the page limit, try exactly 50 pages as well as 51; for cancelling, let a second user try to cancel the first user's job before the owner does.

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.

  1. Question 1 of 6 Which of these questions helps most in the first minutes of a low-level design round about a parking lot?

    Choose one answer.

    Show the answer to question 1

    Answer: What does a driver pay who has lost the ticket?

    Good questions are about behaviour and rules: who does what, the limits, and what happens when something goes wrong. Databases, clouds and services are technology choices for a high-level design round.

  2. Question 2 of 6 "The fee calculator never reads the clock; the exit time is passed in." What kind of requirement is that?

    Choose one answer.

    Show the answer to question 2

    Answer: Non-functional: a quality of the design that makes its results repeatable in tests

    It does not change any fee; it constrains how the calculation is built, so that a test can choose any moment and get the same answer every time. Determinism, thread safety and the cost of one operation are typical non-functional requirements inside one program.

  3. Question 3 of 6 Which of these requirements can a test check as they are written?

    Choose every answer that is right.

    Show the answer to question 3

    Answer:

    • A user may have at most 2 jobs waiting
    • A stay of 15 minutes or less costs nothing

    The 15-minute rule and the limit of 2 waiting jobs each name an exact boundary, so a test can try both sides of it. "Fast" and "easy to use" decide nothing; ask what number or behaviour is meant (for example "each fee is computed without a loop over the hours of the stay").

  4. Question 4 of 6 What does started_hours.py print?

    What does this program print? Choose one answer.

    from datetime import timedelta
    
    HOUR = timedelta(hours=1)
    
    
    def hours_rounded_down(stay):
        return stay // HOUR  # the common mistake: a started hour is dropped
    
    
    def hours_started(stay):
        whole, rest = divmod(stay, HOUR)
        return whole + (1 if rest else 0)
    
    
    for minutes in (59, 60, 61, 125):
        stay = timedelta(minutes=minutes)
        down, started = hours_rounded_down(stay), hours_started(stay)
        print(f"{minutes} min: rounded down {down}, started {started}")
    Show the answer to question 4

    Answer: it prints

    59 min: rounded down 0, started 1
    60 min: rounded down 1, started 1
    61 min: rounded down 1, started 2
    125 min: rounded down 2, started 3

    Floor division (//) drops the part of an hour that has started, so 59 minutes become 0 hours. Counting started hours adds one whenever there is a remainder, and exactly 60 minutes stay one hour. The boundary at 60 minutes is where the two rules agree, which is why a test needs 59, 60 and 61.

  5. Question 5 of 6 Why write down an out-of-scope list as well as the requirements?

    Choose one answer.

    Show the answer to question 5

    Answer: It records what you will not build, so nobody expects it and no time goes into it

    Saying "payments and reservations are out of scope" is a decision both sides can see. It prevents gold-plating and tells the interviewer that you noticed those areas and left them out on purpose.

  6. Question 6 of 6 The prompt says "members can cancel a class booking". Which follow-up question uncovers a rule that the prompt leaves out?

    Choose one answer.

    Show the answer to question 6

    Answer: Until when before the class may they cancel, and does the place go to the next person waiting?

    Cancelling hides a deadline and a consequence: what happens to the freed place. Questions about deadlines, ties and what happens next are where follow-up requirements usually come from.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.