Payout Scheduler: Fixed Intervals, Per-Merchant Schedules and Hold Periods
Company: Stripe
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Technical Screen
You are building the payout calculation for a payments platform. Merchants accept transactions, and the platform periodically pays the accumulated money out to them. The interview builds the calculation in three stages, each extending the previous one; implement each stage as a function.
Assumed input and output shapes (confirm the exact field names with the interviewer):
- A transaction is a record with `id` (string), `amount` (integer number of cents) and `day` (non-negative integer day number). From Part 2 on it also has `merchant`, the name of the merchant it belongs to.
- Transactions arrive in no particular order.
- Each output row is a record whose fields each part lists. Amounts are converted from cents to dollars and printed with exactly two decimal places, so 1349 cents becomes `"13.49"`.
### Clarifying Questions
- In Part 1, is the payout schedule anchored at day 0, so that payouts fall on days `payout_interval`, `2 * payout_interval`, and so on?
- In what order should the transaction IDs inside one payout appear: input order or sorted?
- When two merchants are paid on the same day, in what order should their rows appear?
- Can an amount be negative (a refund), and how should a negative total be printed?
- In Part 3, is a payout that falls exactly `hold_days` after the transaction eligible, and when `hold_days` is 0, does a transaction on a payout day still wait for the next payout?
- What should happen to a transaction whose merchant is not in the merchant list?
### Part 1 — Fixed-interval payouts
Given a list of transactions and an integer `payout_interval`, the platform pays out once every `payout_interval` days. Each transaction is paid on the next payout day after it occurs; a transaction that occurs on a payout day is not part of that day's payout and waits for the next one. Transactions paid on the same day are combined: add up their amounts and record their transaction IDs. Return one row per payout day that has at least one transaction, with fields `day`, `amount` and `transaction_ids`, sorted by payout day. Payout days without transactions are not output.
Example, with payouts on multiples of the interval and `payout_interval = 7`:
```text
transactions:
id amount day
t1 1250 2
t2 500 7
t3 99 6
t4 1000 20
output:
day amount transaction_ids
7 13.49 [t1, t3]
14 5.00 [t2]
21 10.00 [t4]
```
`t2` occurs on payout day 7, so it waits for day 14.
```hint Skip the calendar
You do not need to walk through the days one by one: the payout day of a single transaction can be computed directly from its own day and the interval.
```
```hint Money as integers
Decide when the conversion from cents to dollars should happen, and whether a floating-point number is safe for the running total.
```
#### What This Part Should Cover
- The exact rule that maps a transaction's day to its payout day, including the same-day case
- Grouping by payout day with an exact integer total and the list of IDs
- Formatting cents as dollars with two decimals, and sorting the output
### Part 2 — A schedule per merchant
Now there is also a list of merchants. Each merchant has `name`, `first_payout` (the day of its first payout) and `payout_interval`, and each transaction has a `merchant` field. A merchant is paid on days `first_payout`, `first_payout + payout_interval`, `first_payout + 2 * payout_interval`, and so on. Each transaction goes to its own merchant's next payout day after the transaction's day, with the same rule as Part 1 for a transaction that occurs on a payout day. Aggregate by merchant and payout day: transactions of different merchants are never combined, even when they are paid on the same day. Return rows with `day`, `merchant`, `amount` and `transaction_ids`, sorted by payout day.
Example (the `hold_days` column is used only in Part 3):
```text
merchants:
name first_payout payout_interval hold_days
acme 3 5 4
bolt 2 3 0
transactions:
id amount day merchant
p1 1000 1 acme
p2 250 3 acme
p3 499 3 bolt
p4 1 4 bolt
Part 2 output:
day merchant amount transaction_ids
3 acme 10.00 [p1]
5 bolt 5.00 [p3, p4]
8 acme 2.50 [p2]
```
```hint Before the first payout
Check your payout-day rule on a transaction that occurs before its merchant's first payout, and on one that occurs exactly on it.
```
#### What This Part Should Cover
- The next payout day for an arbitrary first payout and interval, computed in constant time
- A grouping key that keeps merchants apart, and a deterministic order for rows on the same day
- Looking up each transaction's merchant, and what to do with an unknown merchant
### Part 3 — Hold periods
Each merchant now also has `hold_days`. After a transaction occurs, it must wait `hold_days` days before it is eligible for a payout. Each transaction goes to the earliest payout day of its merchant on which it is eligible. Aggregation and output are the same as in Part 2.
With the merchants and transactions above, the Part 3 output is:
```text
day merchant amount transaction_ids
5 bolt 5.00 [p3, p4]
8 acme 12.50 [p1, p2]
```
```hint Check the zero hold
Ask what your rule returns when `hold_days` is 0, and whether that matches Part 2 exactly.
```
#### What This Part Should Cover
- The eligibility rule and its boundary, a payout that falls exactly at the end of the hold
- Consistency with Part 2 when `hold_days` is 0
- Reusing one schedule function across the stages instead of three diverging copies
### What a Strong Answer Covers
- Precise boundary handling: transactions on payout days, before the first payout, and at the end of a hold
- Computing payout days arithmetically instead of simulating the calendar, with the resulting complexity
- Exact money handling in integer cents and correct two-decimal formatting
- A deterministic output order, including ties between merchants on one day and the order of IDs
- Code that evolves cleanly from Part 1 to Part 3, with tests that pin the edge cases
### Follow-up Questions
- Transactions arrive as a stream, day by day. How would you emit each payout on its payout day without keeping every transaction in memory?
- A merchant changes its `payout_interval` from some day on. How does the next-payout calculation change?
- Refunds can make a merchant's total for one payout negative. What would you output, and what would you ask the business?
- How would you test this code so that the boundary rules cannot silently regress?
Overview: A three-part payments coding exercise: group transactions into payouts on a fixed interval, then on per-merchant schedules with their own first payout day, then with merchant hold periods. It tests boundary rules for same-day transactions, exact handling of money in cents, grouping and a deterministic output order.
Read the full Stripe Software Engineer interview experience this question came from