A payment request times out and the client retries it. How do you test that the customer isn't charged twice?
Mid-levelSubmit the same payment twice with an identical idempotency key and assert exactly one charge is created at the provider and one ledger entry is written.
// What interviewers look for
Idempotency framed around the charge and the ledger: the key dedupes the financial side effect, and you test it under genuine retry/timeout conditions, not just a clean resend.
Common pitfall
Confusing this with webhook dedupe, or assuming a timeout means the charge didn't happen — a timed-out request can have succeeded at the provider, so a naive retry double-charges.
Model answer
A network timeout is ambiguous — the charge may have gone through — so the client must retry safely, and the mechanism is an idempotency key. I'd send the same payment body twice with the same key and assert exactly one charge at the provider and one ledger entry, with the second call returning the original result rather than creating a new one. I'd test the timing race: two requests with the same key arriving nearly simultaneously must still produce one charge, so I fire them in parallel. I'd test that different payments with different keys both go through, and that a reused key with a different amount is rejected rather than silently returning the old charge. This is specifically about the money side effect and the ledger write being deduplicated; it's distinct from webhook idempotency, which is about inbound event delivery. Sandbox provider only, no real cards.
idempotencypaymentsledgerretry
A 2.9% processing fee is applied to a $0.01 transaction. How do you test the rounding and fee calculation?
Mid-levelAssert the minimum-fee floor is applied rather than rounding to $0.00, and test sub-cent amounts across the fee formula since rounding errors compound across millions of transactions.
// What interviewers look for
Domain-specific rounding reasoning: the minimum-fee floor, banker's vs naive rounding, and the fact that tiny per-transaction errors aggregate into real money at scale.
Common pitfall
Treating it as a generic boundary-value exercise and missing the fee floor — letting a percentage fee round to $0.00 and processing the transaction at a loss.
Model answer
The trap is that 2.9% of $0.01 rounds to $0.00, so I assert the minimum-fee floor (say $0.30) is applied instead — the transaction either carries the floor fee or is rejected per the product rule, never processed at a loss. I'd build a small matrix of fractional amounts and fee percentages and check each against the documented rounding rule, watching for the boundaries where rounding flips a cent. Because these errors compound, I'd also run an aggregate check: process a large batch and reconcile that the summed fees match the expected total to the cent, catching drift that's invisible per-transaction. I'd cover currency-specific rules — currencies with no minor unit, three-decimal currencies — and FX-converted amounts where rounding happens twice. The distinction from a generic boundary question is that the domain rules (minimum floor, compounding, reconciliation to the cent) drive the cases, not just min/max inputs.
roundingfeescurrencyreconciliation
How would you test daily reconciliation between the internal ledger and the payment provider's statement?
Mid-levelRun a batch of known transactions, then assert the internal ledger total exactly matches the provider's sandbox statement — any discrepancy is a bug, not a report line.
// What interviewers look for
That reconciliation is a correctness test, not optional reporting: you make the diff deterministic with seeded balances and treat any drift as a defect to root-cause.
Common pitfall
Viewing reconciliation as a finance/reporting concern outside QA, or not seeding known starting balances so the comparison is non-deterministic.
Model answer
I treat reconciliation as a first-class test: at the end of a run, the internal ledger must match the provider's statement exactly, and any difference is a defect. To make it deterministic I seed known starting balances and run a controlled set of transactions — successes, failures, refunds, fees — then pull the provider sandbox statement and diff line by line and on the total. I specifically test the edge cases that cause real drift: a failed transaction that still deducted a fee, a webhook that arrived after the reconciliation window closed (it must queue to the next cycle, not vanish), a refund that updated order status but not the ledger, and timezone/cutoff mismatches where a transaction lands in the wrong day. I'd automate the diff in CI so any drift fails the build with the offending entries surfaced. The mindset is that the ledger is the source of truth and reconciliation is how we prove it, every day.
reconciliationledgerbatchcorrectness
How would you test that transactions are handled correctly across account lifecycle states — dormant, reactivated, and closed?
Mid-levelSeed accounts in each lifecycle state and assert state-specific handling: a posting to a dormant account triggers the defined reactivation path, a posting to a closed account is rejected or routed to suspense, and residual balances are handled per policy on closure.
// What interviewers look for
Treating the account as a state machine with distinct posting rules per state — dormant, frozen, and closed are different — and knowing a closed account must never silently accept funds.
Common pitfall
Testing only active-account happy paths, or conflating dormant/closed with the AML-frozen block — so a credit to a closed account is silently applied and the money is stranded with no suspense entry.
Model answer
I model the account as a state machine — open, active, dormant, frozen, closed — and test the posting rules at each state plus the transitions between them, because each state has different correct behaviour. For dormant: an account with no activity past the dormancy threshold should be marked dormant, and an inbound posting should follow the defined path — typically reactivation, where I assert the account flips back to active, the transaction applies, and an audit entry records the reactivation. For closed: a posting must not silently succeed; per policy it's either rejected with a clear error or routed to a suspense account so the funds stay traceable, and I assert one of those happens, never a silent apply to a closed balance. Closure itself needs the residual-balance test: closing an account with a non-zero balance must sweep or refund the residual per policy rather than orphaning it, and a closure attempt with funds still present behaves deterministically. I cover the transition races too — a transaction in flight when the account is closed or marked dormant, and a reactivation that coincides with an incoming batch posting. I deliberately keep this separate from the AML-frozen case, which is a compliance hard-fail on an otherwise-live account; here the concern is the lifecycle of the account itself and where money goes when the account is not in a normal state. Synthetic accounts only, seeded directly into each state so I don't have to wait out real dormancy windows.
core bankingaccount lifecyclestate machine