SDE-1 All-In Guide
Projects and options

Ledgerline: a safe payments ledger

A Go and PostgreSQL ledger that demonstrates idempotency, transaction safety, and reconciliation.

Build a small ledger API in Go and PostgreSQL. Show how it handles retries, concurrent transfers, and incomplete webhook delivery. The project is a learning system, not a real payment service.

Focus: transaction correctness, database design, and failure handling. Plan about 60 to 70 hours across global weeks 2 to 7. The milestones below use project weeks 1 to 6, which map to global weeks 2 to 7.

Use the project reading in the calendar

Read the linked sources in the matching calendar reading slots. Do not add a separate three-hour reading block before the build.

The flow

Loading diagram…

Build spec

Scope

  • Core: accounts, transfers, a balanced journal, idempotency, and a reconciliation command.
  • If time allows: an outbox worker, signed webhooks, and a small failure-injection mode.
  • Cut first: elaborate provider simulation, large randomized workloads, and multiple worker processes.

Show one transfer from API request through database rows and reconciliation. Do not connect the project to real payment credentials or real funds.

The API

  • REST API: accounts, transfers, balances.
  • Double-entry journal: sum(debits) == sum(credits) for every transaction, always. A test checks it, and a nightly reconciliation job checks it again.

Idempotency

  • Idempotency keys on POST /transfers: same key plus same payload gives the same result, with no second side-effect.
  • Store the key in a unique index, insert it first, then apply the transfer in the same transaction.

Locking and money

  • Postgres: SELECT … FOR UPDATE with ordered lock acquisition by account ID. This kills deadlocks.
  • Money is int64 minor units everywhere, never floats.

Outbox and webhooks

  • Transactional outbox: webhook rows are written in the same transaction as the transfer.
  • A worker reads them with FOR UPDATE SKIP LOCKED and delivers with exponential backoff (1 s, 4 s, 16 s, 64 s, with jitter).
  • Each payload is HMAC-signed.
  • Failure simulator: choose whether a callback is dropped, duplicated, delayed, or delivered normally. Use a fixed seed when you want to reproduce a run, and report the cases you tested.

Tests and chaos

  • Failure test: stop the service during a transfer, restart it, and inspect the journal and idempotency result.
  • Property-based test: generate transfers with valid and invalid balances. Check that each committed transaction balances and that retries do not create a second transfer.
  • CLI tool: ledgerline reconcile --from 2026-11-01 prints a drift report.

Benchmarks

  • Measure transfer throughput and p50/p95/p99 latency with a repeatable local workload.
  • Record the hardware, database settings, workload size, and limits beside every result.

Milestones

Project week 1: API skeleton, balanced journal, basic transfer, and integration tests.
Project week 2: Idempotency keys, ordered locking, and concurrent transfer tests.
Project week 3: Add the outbox worker and one reproducible callback failure mode.
Project week 4: Reconciliation command and a report for a deliberately inconsistent fixture.
Project week 5: Restart and retry tests. Fix any invariant violations.
Project week 6: Benchmark, README, design notes, and demo. Publish only measured results.

Artifacts to publish

  • README with an architecture diagram, the idempotency design, the lock-ordering note, results from your own run, and a short demo.
  • docs/ADR-001-idempotency.md, ADR-002-outbox.md, ADR-003-lock-ordering.md.
  • Optional post: "What I learned from testing retries in a small payments ledger".
  • Benchmark script and raw CSV in the repo, so the numbers are re-runnable.

Interview yield

Idempotency design · exactly-once vs at-least-once · transactional outbox · deadlock avoidance · money rounding · ACID in anger · "what happens if your worker dies mid-webhook?"

On this page