Requirements
- Input: a chronological list of events for a single dasher. Each event is roughly
{order_id, dasher_id, timestamp, status}where status is one ofACCEPTED,FULFILLED,CANCELED. Exact input shape (JSON object, list of tuples, timestamp as int vs ISO-8601 string) is deliberately left for the candidate to clarify with the interviewer — this is part of the signal. Use a clean dataclass / struct. - Output: the dasher's total pay for the day. Two flavors:
- Basic: pay = number of minutes the dasher has ≥1 ongoing delivery ×
base_rate(often$0.30 / min). Some variants multiply by the count of ongoing deliveries (so 3 active orders for 1 minute = 3 × base_rate). - Peak-hour variant: a peak window (e.g. 17:00–19:00) doubles the per-minute rate. Intervals that straddle the boundary must be cut at the boundary, not double-counted.
- Basic: pay = number of minutes the dasher has ≥1 ongoing delivery ×
- Edge cases: a single order can be ACCEPTED then CANCELED (no FULFILLED, no pay); CANCELED is treated identically to FULFILLED for the purpose of closing the order's active window.
- The follow-up the interviewer almost always asks: how do you turn this from a batch script into a service called by an upstream system? This is where the production discussion below comes from.
Notes
- Standard approach: maintain
active_count(number of ongoing deliveries) as you sweep events in chronological order. Between consecutive events at timest_prevandt_now, add(t_now − t_prev) × rate × multiplier(active_count)to the total. Apply the event after the addition (an ACCEPTED incrementsactive_count, a FULFILLED / CANCELED decrements it). - Time complexity
O(n log n)for the sort plusO(n)for the sweep; events typically arrive already sorted in the input, so the sort can sometimes be skipped after clarifying. - For the peak-hour follow-up, the cleanest implementation is to split each
[t_prev, t_now]interval at every boundary timestamp inside that interval and apply the per-segment multiplier. Pre-sort the boundary list and usebisectto find the relevant segment. - Common subtle bug: applying the multiplier change at the same instant as a status event without ordering them — break ties by processing event status changes after the time-based accounting for that timestamp.
- Implementing with a state-machine per order (PENDING → IN_PROGRESS → DONE) makes the cancellation logic and the per-order pay reconciliation easier to test.
- A multi-part progression shows up in some loops: Part 1 is the basic per-minute accrual; Part 2 adds
ARRIVEDandPICKED_UPstates so that time the dasher spends waiting at the restaurant is not billed against the other concurrent orders; Part 3 layers on peak-hour double pay. Model each order's lifecycle explicitly so the wait-at-store window is attributed to the right order. - Timestamps as strings (
"2025-12-03 13:15:00") come up in some variants; have adatetime.strptime/dateutil.parser.isoparsesnippet ready or be willing to Google it in the editor — the interviewer typically allows this. - Java solutions run long because of
Map<String, Order>boilerplate; Python is faster to finish bug-free. - Aggressive clarification can legitimately shrink the problem: one phone screen collapsed Part 1 to a simple counter-based solution the interviewer accepted, and the saved time turned into a system-design-flavored conversation. Ask your scoping questions before writing code.
Production follow-ups the interviewer expects you to discuss
- Upstream API failure — exponential backoff with jitter, circuit breaker on the upstream, deadline budget per request, fallback to stale cached data; surface the partial-result vs hard-fail decision.
- Latency SLA when upstream is slow — hedged requests, per-request timeout less than the SLA, async + webhook delivery so the dasher pay endpoint returns immediately.
- Payment safety — idempotency key derived from
(dasher_id, day), transactional outbox, reconciliation job for orphaned payment intents, audit log with monotonic event_id. - Bad / out-of-order input — accept events arriving out of order and re-derive the timeline; treat missing FULFILLED beyond a watermark as CANCELED for accounting; alert if active_count goes negative.
- Memory blow-up on full-day data — stream events instead of buffering; aggregate per-minute totals to a rolling sum and discard processed events.
- Reading all dashers' records — partition by
dasher_id, batch by date, use an aggregation table updated by a daily ETL.
Minimal no-scaffold variant
- A Q3 2026 basic version removes the peak-pay window and uses the saved time for production follow-ups, especially downstream-service failure handling.
- No starter code is provided in this version. Java candidates must create the entry point, domain classes, and a small print-based test harness themselves.
- Only a few simple test inputs may be supplied. Add one or two targeted corner cases after the happy path works rather than relying exclusively on the provided cases.
Preparation
- Write the basic implementation cold in under 25 minutes (sweep on event stream, maintain
active_count, accumulate pay). Time yourself. - Write the peak-hour variant on top of the basic one in another 10 minutes; practice the boundary-split logic.
- Pre-write 3 test cases: (1) one order start-to-finish; (2) three overlapping orders spanning a peak boundary; (3) an ACCEPTED with no FULFILLED before end-of-day. Walking through these test cases out loud is part of the graded signal.
- Memorize 60-second answers for each production follow-up in the list above.
- Pair-program the problem once with a friend acting as a hostile interviewer who interrupts to ask "why this data structure" mid-implementation; this is exactly the round's failure mode.

