Low-Level Design (Object-Oriented Design) Module 5
Modelling lifecycles, time, money and rules interview questions
Interview questions on modelling lifecycles, time, money and rules: warm-ups, then what each harder question tests and how a good answer starts.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
What are the common ways to implement a state machine in code, and when do you pick each one?
Answered in the lesson Implementing lifecycles: State, tables or enums
-
Fresher
Which questions should you ask of every state when you design a lifecycle, and which one do designs most often miss?
Answered in the lesson Designing lifecycles as state machines
-
Fresher
When should code read a monotonic clock, and when the wall clock?
Read a monotonic clock to measure how long something takes: timeouts, waits between retries, rate limits and latency figures. It only moves forward and its starting point means nothing, so only the difference between two readings is useful, and it is never shown to anyone. Read the wall clock when you need a moment that means something outside the program: when an order was placed, whether a hold has passed its deadline, which day a sale belongs to. Store those moments as UTC instants and convert them to a time zone only for display and for rules about local days. Never measure a duration by subtracting two wall-clock readings, because the wall clock can jump. In a design, put both readings behind one injected clock so that tests control them.
From the lesson Time in designs: clocks, time zones and timers
-
Fresher
Why should a payment system not keep amounts in a binary floating-point type such as double?
A double stores fractions in binary, as sums of halves, quarters, eighths and so on, so most amounts of paise or cents, 0.10 included, are kept as the nearest binary value rather than exactly. The error in each value is tiny, but it shows up exactly where money cannot tolerate it: 0.1 + 0.2 is 0.30000000000000004, so totals that should be equal compare unequal, and long sums drift. Keep whole minor units in an integer instead, such as 1999 paise for 19.99 rupees, or use a decimal type such as Python's Decimal or Java's BigDecimal, and keep the currency with the amount. Then rounding happens only where a business rule says, with a mode the code names, instead of wherever the hardware happens to round.
From the lesson Money arithmetic: paise, rounding and splits
-
Fresher
When would you choose a version 7 UUID over a version 4 UUID, and when would you not?
Choose version 7 for keys that a database indexes, such as order or event IDs. It starts with the creation time in milliseconds, so new IDs sort after old ones and inserts land at one end of the index instead of all over it, and any process can still make one without asking anyone, as with version 4. Choose version 4 when an ID must not reveal anything: a version 7 tells everyone who sees it when it was made, which is wrong for an ID that must hide when an account was opened. Use neither as a secret. RFC 9562 says UUIDs should not be assumed hard to guess and must not be used as security capabilities, so a code that opens something comes from a secure random generator, with limits and an expiry of its own.
From the lesson IDs, idempotency keys and deduplication
-
Fresher
Why do versioned rules usually use half-open periods, with the start included and the end excluded?
Because consecutive versions can then share a boundary without overlapping or leaving a gap. The old version ends at the instant the new one starts, and that instant belongs only to the new one, so every moment has exactly one version in force, and the check at load time is simple: sorted by start, each version must begin exactly where the previous one ends. With inclusive ends, either both versions claim the boundary, or the end is written one second or one day earlier, which leaves a gap below that unit. Half-open periods also add up: a year split into months counts no instant twice. Store the boundaries as instants, in UTC or with their offset, so that a change at local midnight is one moment for every server.
From the lesson Rules as configuration: tables and effective dates
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Mid to senior
One service holds two lifecycles. A payment has seven statuses fixed by the payment provider, and twelve functions branch on them. A support ticket's statuses and moves are changed by the support team every month, and an auditor checks each change. How do you implement each one, and how do you defend the choice?
- What it tests
- Choosing an implementation by what changes and who changes it, exhaustive matching against data-driven tables, and validating rules that arrive as data.
- First step
- Ask what changes in each lifecycle. The payment statuses are fixed while the code over them grows, so a closed set with exhaustive matches lets the compiler find every function to update. The ticket rules change monthly and are checked by people who do not read code, so they belong in a transition table kept as data.
- Follow-ups
-
- The support team wants to change the ticket table without a deploy. What do you check when it loads?
- The payment provider adds an eighth status. How do you ship it safely?
- Where do side effects such as emails and refunds live in the table version?
From the lesson Implementing lifecycles: State, tables or enums
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
A refactor renamed the class OccupiedState to HoldingParcel. After the deploy, 40,000 of 2 million stored compartments fail to load, and the others load fine. What happened, how do you recover today, and how should statuses be stored from now on?
- What it tests
- Diagnosing storage that depends on class names, recovering without losing data, choosing stable codes over names and positions, and handling unknown codes during a rolling deploy.
- First step
- Look at what the failing rows store and why only 2 % of them fail. If the status column holds class names, only the occupied rows broke, because only that class was renamed. That also gives the fastest recovery: let the loader map the old name to the new class, deploy, and only then move every row to a stable code.
- Follow-ups
-
- Why not store the position of the status in its enum instead?
- During the rolling deploy, old servers read rows that new servers wrote. What can go wrong?
- How do you make sure nobody changes a stored code by accident later?
From the lesson Implementing lifecycles: State, tables or enums
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A payment provider delivered its success callback for one booking twice, 40 seconds apart. Customers received two ticket emails, and the logs show an error for the second callback. Redesign the booking's lifecycle so that duplicates are harmless, and say how you would test it.
- What it tests
- Treating resent events as normal input, keying effects to event or request ids, ordering a transition and its side effects, and testing duplicates on purpose.
- First step
- Find out what identifies a copy. The provider's callback carries its own event or payment id, so store each id you have processed with the reply you gave, and look it up before anything else. Then ask why an email went out twice although the second callback failed: the email was sent before the transition, not after it.
- Follow-ups
-
- The two callbacks arrive at the same moment on two servers.
- The second callback says the payment failed.
- How long must processed ids be kept?
From the lesson Designing lifecycles as state machines
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Seat holds expire after 8 minutes. A customer pays at 7 minutes 58 seconds, but the provider's callback reaches you at 8 minutes 3 seconds, after the hold has expired. What should happen, and how does your state machine make that outcome a decision rather than an accident?
- What it tests
- Treating time as an event, choosing a policy for late events and stating its cost, modelling a grace period as a state, and testing both sides of a boundary.
- First step
- Separate the two times: the provider's timestamp says when the customer paid, and the arrival time says when you heard about it. Then name the options, a final deadline with an automatic refund or a short grace state that accepts payments made before the deadline, and ask what each one costs.
- Follow-ups
-
- The seats were sold to someone else in those 5 seconds.
- How do you test this without waiting 8 minutes?
- The scheduler that expires holds was down for 10 minutes.
From the lesson Designing lifecycles as state machines
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
Support must answer "who cancelled this order, when and why?" for any order of the last two years. Design how the order's lifecycle records its transitions, and explain how the current status and the history stay consistent.
- What it tests
- Designing an append-only transition history and its fields, keeping a status column and the history in step, and checking them against each other.
- First step
- List what support needs from each transition: the states before and after, the time, the actor, the reason and the request that caused it. Store those as rows that are only ever inserted, then decide whether the current status is a column written in the same transaction or read from the last row.
- Follow-ups
-
- The orders table has 50 million rows. Is the history affordable?
- Someone fixed a status by hand in the database.
- How do you prove that the history and the status agree?
From the lesson Designing lifecycles as state machines
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to mid
A cart releases its seats after 10 minutes. Its test sleeps for 10 minutes, so it runs only at night, and it still fails now and then. Redesign the code and the test so that the check is fast and reliable.
- What it tests
- Injecting a clock, storing an absolute expiry, choosing the boundary rule, and testing just before, at and after a deadline without real waiting.
- First step
- Find where the cart reads the time. If the expiry rule calls the system clock, the test cannot control time and has to wait. Pass a clock into the cart, store the expiry as an instant, and let the test set the clock to the instants it wants to check.
- Follow-ups
-
- Why does the sleeping test fail now and then?
- Expiry is done by a background job. How do you test that job?
- How do you check that the production code uses the injected clock everywhere?
From the lesson Time in designs: clocks, time zones and timers
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
Statements must be generated at 01:30 local time every day for customers in New York and in Mumbai. Your scheduler stores the next run as the last run plus 24 hours. What goes wrong around New York's two clock changes, and how do you make sure each customer gets exactly one statement per local day?
- What it tests
- Seeing that local days are not always 24 hours long, scheduling by local date in a named zone, handling skipped and repeated local times, and making each run idempotent.
- First step
- Work out the next runs in New York across a change. The day the clocks go forward has 23 hours, so adding 24 hours moves the run from 01:30 to 02:30 local time; the day they go back has 25, so it moves to 00:30. Mumbai never changes its offset, which hides the bug in testing there.
- Follow-ups
-
- A customer moves from New York to Mumbai.
- The scheduler was down from 01:00 to 03:00.
- How do you test this without waiting for the change day?
From the lesson Time in designs: clocks, time zones and timers
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
One night, time synchronisation stepped a server's clock back by 2 minutes. For the next few minutes, payment calls that hung were not timed out, and some request durations in the logs were negative. Explain both symptoms, and change the design so that it cannot happen again.
- What it tests
- Telling wall and monotonic clocks apart, finding every duration measured on the wall clock, and keeping timestamps and durations correct side by side.
- First step
- Both symptoms come from durations computed as the difference of two wall-clock readings. A step back of 120 seconds makes every interval that spans the step 120 seconds shorter, so a 30-second timeout fires only after 150 seconds and short calls measure below zero. Find every such subtraction first.
- Follow-ups
-
- The logs still need the time of day of each request.
- Should you stop time synchronisation from stepping the clock?
- How would you write a test that catches this?
From the lesson Time in designs: clocks, time zones and timers
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to mid
A bill of 1,000 rupees is shared equally by three friends, and another of 100 rupees by the ratio 3 : 2 : 2. Design a split function whose parts always add up to the bill, and say who receives each leftover paisa and why.
- What it tests
- Working in whole paise, the largest remainder method, a stated and stable rule for leftover paise, and the properties that a test can check.
- First step
- Work in whole paise: 1,000 rupees is 100,000 paise, which divides by three as 33,333 with 1 paisa left over. Give every part the whole paise of its exact share first, then decide, by a written rule, which parts receive the paise that are left.
- Follow-ups
-
- The friends want the payer to absorb the difference instead.
- How do you test the function?
- The shares must be whole rupees, for cash.
From the lesson Money arithmetic: paise, rounding and splits
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Your billing service shows 18.00 rupees of tax on an invoice line of 100.05 rupees, but a customer's spreadsheet shows 18.01. The line carries 9 % CGST and 9 % SGST. Explain the difference, and decide where your system should round and how it should show the result.
- What it tests
- Seeing where rounding happens, comparing rounding per tax head with rounding once, choosing one rule and keeping invoice, ledger and reports consistent.
- First step
- Compute both ways. The service rounds each 9 % to paise, 9.0045 to 9.00 twice, and adds the two; the spreadsheet takes 18 % of the line, 18.009, and rounds it once to 18.01. Both follow a rule. The problem is that two systems that must agree follow different ones.
- Follow-ups
-
- Which method should the invoice use?
- The ledger totals 1 million such lines a month. How big can the gap get?
- How do you make sure reports never recompute the tax another way?
From the lesson Money arithmetic: paise, rounding and splits
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A Node.js service keeps balances as JavaScript numbers in rupees, such as 1234.5. Some totals end in long strings of digits, and a large merchant's monthly settlement is a few paise off. What is wrong, what do you change in the code and in the API, and how do you migrate safely?
- What it tests
- Seeing the binary floats behind JavaScript numbers, moving to integer paise or BigInt, choosing an API representation, and migrating stored values without changing a paisa.
- First step
- Show the cause in one line: 0.1 + 0.2 is 0.30000000000000004, because JavaScript numbers are doubles. Amounts in rupees with fractions are approximations, and every addition rounds. Move the arithmetic to whole paise first, then decide how amounts travel in the API.
- Follow-ups
-
- Is a Number in paise safe, or do you need BigInt?
- How should the API send amounts?
- How do you convert the stored rupee values?
From the lesson Money arithmetic: paise, rounding and splits
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A wallet app retries a top-up when the network times out. Support finds that about 3 in 10,000 top-ups are credited twice. Design the fix in the wallet service's classes, and say how you would test it.
- What it tests
- Idempotency keys made by the client, what the server stores per key, where the key record is written relative to the credit, and tests that retry and crash on purpose.
- First step
- Ask what a retry carries. If the app builds a new request each time, the server cannot tell a retry from a second top-up, so the first change is in the app: one key per top-up, sent again with every retry. Then the service can look the key up before it credits anything.
- Follow-ups
-
- Two copies of the same top-up reach two servers at the same moment.
- The service crashes after crediting but before saving the reply.
- How long do you keep the keys, and how much storage does that take?
From the lesson IDs, idempotency keys and deduplication
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
Design the pickup codes for a network of 40,000 parcel lockers: what a code looks like, how it is generated, how long it lives and what limits wrong attempts. Today the codes are six digits from the random module and never expire.
- What it tests
- Treating a code as a permission, choosing its generator, the odds of a guess under an attempt limit, uniqueness among live codes, expiry, and where each limit is enforced.
- First step
- Start from what a code grants: it opens a door, so knowing it is permission. That decides the generator, and it means guessing is limited by two numbers the design controls: how many codes there are, and how many wrong attempts a locker allows before it stops accepting codes.
- Follow-ups
-
- Should a code be unique across all lockers or only within one locker?
- A recipient mistypes the code three times. What does the locker do?
- Why not use the first six characters of a UUID?
From the lesson IDs, idempotency keys and deduplication
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A consumer drops duplicate order events by keeping their IDs in memory for 10 minutes. After an outage, the broker redelivered two days of events and some customers were charged twice. What went wrong, and how do you redesign deduplication so that an outage cannot do this again?
- What it tests
- Seeing that a window's length is a promise about redelivery, estimating what a window holds, and choosing between event-ID windows, sequence numbers per sender and effects keyed by a business identifier.
- First step
- Compare the window with the redelivery. Copies that arrive more than 10 minutes after the original look new to the consumer, so every redelivered event of the two days was processed again. Before choosing a design, work out what a window long enough would hold at the real event rate.
- Follow-ups
-
- The events carry a sequence number per producer. How does that change the design?
- The charge is made by a payment provider, not in your database.
- How would you test the fix?
From the lesson IDs, idempotency keys and deduplication
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
An operator fixed a delivery fee by editing the version that has been in force for three months, instead of adding a new one. Month-end reports now disagree with invoices that customers already hold. How should rule versions be stored and changed so that this cannot happen, and how would you notice if it did?
- What it tests
- Treating a version that priced bills as fixed, recording on each bill what priced it, refusing such edits when a file loads, and repairing data after the fact.
- First step
- Ask what a bill needs in order to be priced again exactly: its inputs and the version that priced it. If bills keep only a total and the table can be edited, nothing shows which bills changed. Start by recording the version on every bill, then decide what a new rule file is allowed to change.
- Follow-ups
-
- The edit was right: customers had been overcharged. What now?
- Closing an open-ended version needs an end time. Is that an edit?
- How do you keep the history of versions auditable for years?
From the lesson Rules as configuration: tables and effective dates
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A discount file with "min_total": "5OO", the letter O typed for two zeros, reached production, and for two hours every basket got the discount. Design how the service loads rule files so that a bad file never takes effect, at start-up or when a new file arrives while it is running.
- What it tests
- Checking shape and meaning when a file loads, refusing to start on errors, keeping the last good rules on a bad reload, and making the problem visible to the right people.
- First step
- Find out how a string reached the comparison: the loader trusted the file. Define loading as parsing, checking the shape against a schema, checking what a schema cannot express, and only then swapping the new rules in whole. Then decide separately what start-up and a reload do with a bad file.
- Follow-ups
-
- Why did a typo make every basket qualify?
- The new file is bad and the service is already running.
- Who should see the error, and when?
From the lesson Rules as configuration: tables and effective dates
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A tax rate falls at midnight. An order is placed and paid at 23:58, invoiced at 00:10, and the goods ship two days later. Which rate applies, who decides, and how does your design keep that decision out of the pricing code?
- What it tests
- Seeing that choosing the instant is a business or legal rule, separating it from the rate lookup, recording the choice on the invoice, and testing every combination around a change.
- First step
- Split it into two questions: which instant selects the rate, and what the rate is at that instant. The second is a table lookup. The first belongs to whoever owns the rule, often the law, and the time the order was placed may not be one of the dates that count. List the dates the rule names before choosing.
- Follow-ups
-
- Where does the policy live, and how is it chosen for a product?
- The rule for choosing the instant changes next year. What changes in the code?
- How do you test it?
From the lesson Rules as configuration: tables and effective dates
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
All Low-Level Design (Object-Oriented Design) interview questions · The Low-Level Design (Object-Oriented Design) track