Code Review: A Ride Fare Method That Charges a Card Then Logs the Trip
MercorSoftware Engineer · Technical ScreenLast updated
Review a Java method that charges a rider's card through an external processor and then writes a trip record, and explain what goes wrong if the caller fails mid-call and how to fix it.
A ride-hailing backend has this Java service, which captures a rider's fare through an external card processor and then logs the completed trip. The interviewer asks: what happens if the process calling this method crashes or times out partway through, and how would you change the code? This is a discussion round — no new code needs to compile, but be specific about the fix.
import java.util.Map;
public class FareCaptureService {
private final CardProcessor processor;
private final Database db;
public Map<String, String> captureFare(String riderId, int fareCents) {
String captureId = processor.capture(riderId, fareCents);
db.insert("trips", Map.of(
"rider_id", riderId,
"fare_cents", fareCents,
"capture_id", captureId
));
return Map.of("status", "ok", "capture_id", captureId);
}
}
Assume processor.capture makes a network call that moves money and returns a processor-assigned capture ID, throwing on failure, and db.insert writes a single row with no further side effects.
Clarifying Questions
- Is "the caller" the dispatcher service invoking
captureFare(which may retry on timeout), orcaptureFare's own connection to the card processor? - Can the card processor accept an idempotency key, or let us look up an existing capture by a reference value we provide?
- What should the dispatcher be told when the outcome of a capture attempt is genuinely unknown (request sent, response never received)?
- Can the dispatcher attach a unique ID to each fare-capture attempt before calling this method?
What a Strong Answer Covers
- Every point between and around the two calls where a crash or network failure leaves the system in an inconsistent state (money moved but untracked, money moved twice, or genuinely unknown outcome).
- Why simply wrapping both calls in a database transaction does not make them atomic — the processor call isn't part of that transaction and can't be rolled back by it.
- An idempotency key generated once per fare-capture attempt, enforced with a uniqueness constraint in the database and passed through to the processor so a retried call is recognized as the same attempt rather than a new charge.
- What the method should return (or raise) when the capture succeeded but the database write failed, versus when the capture call itself timed out with an unknown outcome.
- A reconciliation path: a background job that compares the processor's record of captures against the
tripstable and resolves rows left in an unrecorded or unknown state.
Hints
Hint 1
Draw a timeline with three points: before processor.capture, between the two calls, and after db.insert — write down what state the processor and the database are in at each point if the process dies right there.
Hint 2
A caller that times out waiting for captureFare will very likely retry with the same inputs — decide what would let the system recognize that retry as the same fare rather than charging the rider again.
Hint 3
A transaction around db.insert alone can't undo processor.capture if it already succeeded — atomicity across the two systems needs a different mechanism.
Hint 4
Consider what the method should return when it genuinely doesn't know whether the charge succeeded (e.g. the processor call times out) — 'ok' and 'failed' aren't the only two honest answers.
Follow-up questions
- How would you design the idempotency key itself — client-generated UUID, or server-generated and returned before the first attempt?
- What would you change if the card processor did not support idempotency keys at all?
- How does this design change if captureFare can be called concurrently for the same rider from two different requests?
- How would you test the retry-after-timeout path without a real flaky payment processor?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.