Home/Low-level design/Banking System

Banking System

Atomic transfers, validation, and idempotency

An atomic transfer validates both accounts and available funds before applying either debit or credit. A successful request registry is a separate source of truth: retries return the original result even after later balances change.

go
type TransferResult struct { SourceBalance, TargetBalance int }
original := TransferResult{SourceBalance: 70, TargetBalance: 30}
fmt.Println(original.SourceBalance)

Only successes consume request IDs. A failed insufficient-funds attempt can succeed after a deposit, while a successful identifier reused with different arguments is an error. The outgoing total counts transferred principal once, including across retries. Balances conserve money across internal transfers; deposits intentionally add money. Ranking must use numeric spending followed by stable account identity.

Go models this service with a struct and pointer-receiver methods. Constructor functions return a pointer; validation failures return errors rather than panicking. Scalars in snapshot structs are copied by value. Maps, slices, and optional pointers still require detached copies when crossing the service boundary. Go maps do not have key iteration order; use ordered slices for ranked results. There are no threads, persistence, wall-clock reads, or external side effects in this exercise.

Put it into practice

Implement the exercise in a full-screen workspace, then run its tests.

Open practice
Next lessonTransactional Key-Value Store