Home/Low-level design/Banking System

Banking System

Atomic transfers, validation, and idempotency

Ownership and transitions

A transfer commits two balances, one outgoing total, and one successful replay record together. Store the successful result rather than recomputing it on retry: later deposits must not change a retry's return value. Failures consume no request ID. A fingerprint distinguishes exact retries from conflicts. Point operations are expected O(1), ranking O(A log A), replay retention O(R).

Java boundaries

Use the supplied nested records as immutable value carriers. A record is only shallowly immutable: copy every retained mutable input and every outward list/map, including lists inside records. Absence is null; validation failures use IllegalArgumentException, missing-key purchase errors use NoSuchElementException, and invalid transaction boundaries use IllegalStateException. Java primitive types already exclude booleans from numeric inputs. Numeric values use long where signatures say long and all computations must fit signed 64-bit values. Calls in these service exercises are serialized.

A successful replay returns its original result rather than recalculating with current balances. Result values and live account state are different records:

java
import java.util.*;

class Example {
    public static void main(String[] args) {
        record Result(long source, long target) {}
        Result original = new Result(70, 30);
        long currentSource = 79;
        assert original.source() == 70;
        assert currentSource != original.source();
    }
}

A deposit after the first transfer can change the live balance without changing what an exact retry must return. The request fingerprint must also remain immutable so conflicting reuse is rejected.

Design boundary

The atomic boundary includes source debit, target credit, spending total, and replay installation. Failed transfers reserve no request ID. Durable financial accounting would require a ledger and crash-recovery guarantees; a serialized in-memory model cannot promise either.

Put it into practice

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

Open practice
Next lessonTransactional Key-Value Store