0unveiled · Candidate report
Aarav Kulkarni
Junior Backend Engineer · Acme Fintech · github.com/aaravk · 9 Oct 2026
Role context: Node and Postgres. Owns a payments service. Must handle on-call.
Owns and understands the work they claim. One gap to probe.
Reviewed by a founder: Ayush
Strong on the parts that matter for an on-call payments role. Spend your final round on money handling and one live change; skip the basics.
Two-minute read
Hire signal: YesBuilt and can defend a payments ledger; one gap on money rounding.
Strengths
- Explained webhook idempotency without a hint
- Walked through the retry queue’s failure path under a Redis outage
- Changed the refund logic live to lock the account row
Concerns
- Needed a hint on the rounding rule for paise
Verified in the interview
- Explained why webhooks are idempotent by event id and what breaks without it (src/webhooks/handler.ts).
- Described the retry queue's failure path under a Redis outage and the dead-letter handoff (src/queue/retry.ts).
- Changed the refund path live to lock the account row before checking the refundable amount (src/ledger/balance.ts).
Could not explain
- Why the ledger uses integer paise: answered 'precision' but did not know the half-up rounding rule in src/ledger/money.ts.
Ask them these three
“What does toPaise return for 10.005 rupees, and why?”
“Two refunds for the same charge land at once. What would you change so the balance can’t go negative?”
“If transaction volume doubled tomorrow, which table hurts first and why?”
AI use
Declared: Cursor. Detected: AI co-author trailers on 118 of their commits. Verdict: Declared Cursor for scaffolding and tests; co-author trailers on 38% of commits. Understood and extended the generated code; no penalty.
Interview integrity
No integrity concerns
Measured: median 2.9 s before answering · stayed on the tab · 0.4 keyboard-like sounds per minute.
Signals, not verdicts: a nervous candidate or a noisy room can trip them. Use them to decide what to probe.
Flags
- Possible secrets committed in aaravk/ledger-api. 1 hardcoded secret pattern found in an early commit (rotated since). Ask how secrets are handled now.
Interviewer summary
Owns the payments ledger and can defend its design under follow-up. The one gap is money representation, which a short probe will settle.
Full evidence
Repositories
| Repository | Language | Commits | Theirs | Lines theirs | Critical |
|---|---|---|---|---|---|
| aaravk/ledger-api | TypeScript | 343 | 91% | 91% | 0 |
| aaravk/ledger-ui | TypeScript | 897 | 84% | 83% | 0 |
Rubric scores (0 to 3 per item)
- Trace it · src/webhooks/handler.ts3/3
Walk me through what happens in handleWebhook when the same Stripe event arrives twice within a second.
Evidence: “the event id is the idempotency key, the second insert hits the unique index and we return 200 without touching the ledger”
Specific and anticipates the caller's view.
- Why · src/ledger/money.ts1/3
Why does the ledger store integer paise instead of decimals, and what is the rounding rule in toPaise?
Evidence: “for precision, decimals lose stuff”
Did not know the rounding rule.
- What if · src/queue/retry.ts2/3
If Redis is unreachable while scheduleRetry runs, what happens to the webhook that triggered it?
Evidence: “the handler throws so Stripe retries and nothing is written because it is all one transaction”
Correct; did not mention the circuit breaker.
- Change it · src/ledger/balance.ts3/3
Two refunds for the same charge land at the same time. What would you change in applyRefund so the balance cannot go negative?
Evidence: “select the account row for update, check the remaining refundable amount inside that lock, and fail with a typed error”
Named the lock scope unprompted.
- Debug · src/queue/retry.ts2/3
Your refund handler retries five times. What happens on the sixth failure, and who finds out?
Evidence: “it goes to the dead-letter list and emits refund.failed, which pages on-call”
- Trade-off · src/ledger/entries.ts2/3
If transaction volume doubled tomorrow, which table hurts first and why?
Evidence: “ledger_entries, because balance reads scan every entry for the account”
- AI diff · src/webhooks/handler.ts3/3
Which parts of handleWebhook did Cursor write, and what did you change afterwards?
Evidence: “Cursor wrote the parsing and the switch; I added the transaction and the idempotency insert after we double-applied a refund in staging”
- Why · src/hooks/useBalance.ts2/3
Why does useBalance refetch on window focus but not on an interval?
Evidence: “balances rarely change, so focus refetch is enough and the socket pushes updates”