Match Payments to Invoices by Memo ID, Exact Amount, then Amount Tolerance
Company: Stripe
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Onsite
You are given one payment record and a list of invoice records, each encoded as a string. Parse them, find the invoice the payment should be applied to, and return a result string that describes the match. The task grows over three parts. Each part extends the previous code, keeps the same output format, and must keep every earlier example passing.
- A payment has a payment ID, an amount and a memo. The memo is free text that may contain the ID of the invoice being paid.
- An invoice has an invoice ID, an amount and a due date.
The exact string formats of the original prompt were not preserved. For practice, assume the following, and confirm the details with the interviewer before coding:
- A payment string is `"<payment_id>,<amount>,<memo>"`, for example `"p1,500,Paying off: INV-4"`. The memo is everything after the second comma.
- An invoice string is `"<invoice_id>,<amount>,<due_date>"`, for example `"INV-4,500,2024-04-01"`, with the due date written as `YYYY-MM-DD`.
- Amounts are non-negative integers in the smallest currency unit.
- The result for a match names the payment, its amount, the matched invoice and that invoice's due date, for example `"payment p1 (amount 500) pays invoice INV-4 due 2024-04-01"`.
After each part, the interviewer asks you to invoke your method on example inputs (including the last example in the prompt), to confirm that the original use cases still pass alongside the new requirements, and then to look for further edge cases and potential bugs in your implementation.
### Clarifying Questions
- What should the method return when no invoice matches the payment?
- Can a memo mention more than one invoice ID, or an ID that is not in the invoice list?
- Is each payment matched on its own, or can an invoice that one payment already matched be matched again by another payment?
- If two candidate invoices also share the same due date, which one wins?
### Part 1 — Match by the invoice ID in the memo
Implement `reconcile(payment: str, invoices: list[str]) -> str`. Parse the payment and the invoices, find the invoice whose ID appears in the payment's memo, and return the result string.
```hint Separate parsing from matching
Turn each string into a typed record first (amounts as numbers, due dates as comparable values), so that later parts only change the matching step. Then think about how to find an invoice by its ID without rescanning the whole list.
```
#### What This Part Should Cover
- Parsing into typed fields, including a memo that itself contains commas
- Extracting an invoice ID from free text, and handling an ID that is missing or unknown
- Producing the result string exactly as specified, then running the examples
### Part 2 — Add exact amount matching
Keep the memo matching from Part 1, and add a rule that matches the payment to an invoice whose amount equals the payment amount exactly. If several invoices have that amount, choose the one with the earliest due date. The output format does not change, and the Part 1 examples must still pass.
```hint Where the tie-break lives
The invoice list is not guaranteed to be sorted. Decide how the earliest-due-date rule is applied so that the answer does not depend on input order.
```
#### Clarifying Questions for this Part
- If the memo names a valid invoice and a different invoice matches the amount exactly, which rule wins?
- If the memo names a valid invoice whose amount differs from the payment amount, is that still a match?
#### What This Part Should Cover
- An explicit, justified precedence between memo matching and amount matching
- An earliest-due-date selection that is independent of input order
- Re-running the Part 1 examples as regression tests
### Part 3 — Add a forgiveness tolerance
Add a forgiveness value: the payment may now also match an invoice whose amount lies within the allowed range around the payment amount. If several invoices fall inside that range, choose the one with the earliest due date. The interviewer then asks: what if there is an exact amount match and also a match once forgiveness is applied? How would you prioritize them?
```hint Exact is also in range
An invoice with the exact amount also lies inside the tolerance range. Build a small example in which that changes which invoice the earliest-due-date rule selects, and decide which outcome you want.
```
#### Clarifying Questions for this Part
- Is forgiveness an absolute amount or a percentage, and is the boundary of the range inclusive?
- Does the range apply to both underpayments and overpayments?
#### What This Part Should Cover
- Precise tolerance semantics: units, boundary and direction
- A defended priority order among memo, exact amount and tolerance matches
- Tests that pin down the priority decision as well as the Part 1 and Part 2 behavior
### What a Strong Answer Covers
- Parsing kept separate from matching, so that each new rule is a small, local change
- Deterministic results: explicit precedence, stable tie-breaking and a defined no-match result
- Examples run after every part, with earlier cases kept as a regression set
- Proactively found edge cases: malformed or ambiguous memos, unknown IDs, duplicate amounts and due dates, money representation
- Clear communication of each policy decision the prompt leaves open
### Follow-up Questions
- If each invoice can be paid only once, how does reconciling a batch of payments change, and does the processing order affect the result?
- How would you handle a partial payment, or one payment that covers several invoices?
- With millions of invoices, how would you index them so that exact and tolerance lookups stay fast?
- How would you make the order of the matching rules configurable without rewriting the matcher?
Overview: A multi-part coding exercise that matches a payment to an invoice: first by an invoice ID found in the payment memo, then by exact amount with the earliest due date breaking ties, then within an amount tolerance. It tests parsing, deterministic tie-breaking, rule precedence, and keeping earlier test cases passing as requirements grow.
Read the full Stripe Software Engineer interview experience this question came from