Learning pathsPricingChecking sign-in…
← LLD problem library

LLD-018 · LOW LEVEL DESIGN

Wallet / Payment Ledger

Preserve balanced postings and available funds across holds, capture, release and reversal.

Local Java 21 reference verified: 227 checks. Learner drafts are not executed. Author self-review; independent review pending.

Existing introductory lesson · Version 1

Understand the problem

Design a local one-currency wallet ledger with funded accounts, atomic transfers, reservations, capture/release and append-only reversals. Distinguish posted from available balance, prevent concurrent overspending and explain why a compensating transfer can fail after funds were spent or reserved.

Before you start

  • Exact integer money
  • Balanced postings and immutable history
  • Atomic multi-account updates
  • Idempotency and reservation lifecycle

Scope and explicit decisions

  • Synthetic one-currency in-memory ledger. It does not connect to banks/payment rails, authenticate users, convert currencies, reconcile external statements or implement a production regulated account system.
  • Constructor funding is a fixture. An internal @clearing contra-account offsets all initial wallet balances so opening postings sum to zero. Aggregate initial funding must fit a nonnegative long; this bounds the model’s capital. The clearing account cannot be used as a normal transfer wallet.
  • Money is integer minor units. Wallet balances and active reserves stay nonnegative; available=posted-reserved. Every normal posting conserves total value including the clearing account. No overdraft or new external deposit/withdrawal API is implemented.
  • Transfers atomically debit and credit two different registered wallets. A reservation does not post a transfer; it reduces available funds. Capture atomically releases that reservation and posts its transfer to a different wallet.
  • Hold release is idempotent by hold identity. Captured holds cannot be released, and released holds cannot be captured. Holds have no TTL or automatic cleanup; a higher-level owner must resolve abandoned reservations.
  • Transfer/reserve/capture/reverse keys share a ledger namespace and use typed fingerprints. Matching retries return the original posting or current hold identity; conflicting operation reuse rejects.
  • Reversal appends an inverse of an original non-opening posting once. It must satisfy current available-fund constraints, so it may fail when the recipient spent or reserved the money. Reversing a capture does not reactivate its historical hold.
  • All checked balance updates are prepared in a temporary map before publication. Histories, holds and operation results are retained in memory; no restart durability or retention policy is provided.

Worked examples

Input: A100/B0, transfer30 to B, reserve50 from A.

Outcome: A posted70/available20; B30; clearing-100. Capture50 gives A20/B80.

A reservation changes spending power before it changes posted ownership.

Input: B reserves all80, then reverse the captured50.

Outcome: Reversal rejects. After releasing B’s hold, inverse posting restores A70/B30.

Compensation is another constrained business transaction, not guaranteed rollback.

Try before revealing

Hint 1

Where does the opposite side of opening funding appear?

Hint 2

Which funds are posted but unavailable?

Hint 3

Can capture and release both win one active hold?

Requirements and invariants

R1 — Every posted transaction conserves value and projection equals ledger replay. transfer protects I1.

R2 — Normal wallets cannot spend or reverse below active reserved funds. nextBalances protects I2.

R3 — Capture and release cannot both consume one active hold. capture protects I3.

R4 — Retries and corrections retain stable identities without rewriting original history. reverse protects I4.

I1: Every posting delta and the full balance map including clearing sum to zero; rebuild equals the materialized balances.

I2: For each wallet, 0<=reserved<=posted, hence available>=0.

I3: A hold transitions ACTIVE→CAPTURED or ACTIVE→RELEASED once; only ACTIVE contributes to reserved funds.

I4: Each operation key maps one typed request to one result, and each original posting has at most one accepted reversal.

Derive the design

An available-balance number is not an accounting model

A simple map of wallet totals can check a debit, but without immutable balanced entries it cannot explain history or recover a projection. The reference stores postings plus a rebuildable balance map, then separates reserved funds from posted value. One monitor protects the multi-account/hold transition; a concurrent map alone would not prevent compound overspending.

Alternative: Per-account locks can improve unrelated transfer throughput, but transfers need stable lock ordering and coherent idempotency/hold metadata. A durable ledger needs real transactional storage and tested isolation; an in-memory fake cannot prove those guarantees.

Choose account and amount semantics first

State currency, minor units, balance signs, overdraft and source of opening funds. The clearing entry makes conservation explicit instead of hiding initial value creation.

Separate encumbrance from ownership transfer

A hold is not an external charge or a posted transfer. Keeping reserved and posted separate makes available-fund checks precise.

Prepare every checked update before commit

Use a temporary next-balance map and a reserved map appropriate to capture. Validate all wallets before changing projection, posting and operation result.

Keep corrections visible and fallible

A reversal may fail under current availability. Preserve history and expose that failure rather than deleting the original posting or violating another active promise.

Responsibilities and ownership

Ledger

Owns: Balances, reservations, postings, dedupe results and reversal index

Methods: transfer, reserve, capture, release, reverse

One local atomic owner coordinates all involved wallets.

Posting

Owns: Immutable balanced deltas and optional original-reference

Methods: Record accessors

Original financial facts are not edited by correction.

Hold

Owns: Wallet, amount and reservation lifecycle identity

Methods: Immutable state snapshots

Not a posted debit until captured; no hidden expiration.

Typed operation fingerprints / clearing

Owns: Retry meaning / opening-value offset

Methods: Structured equality / immutable opening posting

Fixture funding is not an external deposit or bank reconciliation.

Atomic wallet posting

Synthetic local accounting only; no external banking settlement is implied.

  1. Typed transfer/capture request
  2. ↓ Resolve retry identity

    Prepare zero-sum deltas
  3. ↓ Account for active holds

    Validate all available balances
  4. ↓ One local transaction owner

    Commit posting + projection + result
Hold lifecycle

Only one terminal path wins. Reversing a capture posting does not reactivate the hold.

ACTIVE reserved funds

Atomic debit/credit and reserve release

CAPTURED: transfer posted

No posting; clear encumbrance

RELEASED: availability restored

Data model, schema and relationships

A wallet projection is derived from immutable balanced journal entries. Holds encumber available funds without posting money; capture converts an active hold into a balanced transfer exactly once.

Implementation boundary: The reference uses signed exact deltas, one synthetic clearing account and local reservation maps. These tables describe internal one-currency accounting; external settlement and regulated account types are separate.

These are proposed logical schemas with field contracts, not executable database migrations. PK identifies a row, FK references an existing parent, UK is a uniqueness rule. A composite PK identifies the combination. Nullable references are marked with ?. A foreign key protects identity; cross-row limits, timing and state transitions also require the transaction rules below.

wallet_account: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

wallet_account relationshipswallet_account 1 to journal_line 0..*: receives deltas. journal_line.account_id → wallet_account.id. wallet_account 1 to wallet_hold 0..*: encumbers funds. wallet_hold.account_id → wallet_account.id. wallet_account 1 to wallet_operation 0..*: initiates operations. wallet_operation.account_id → wallet_account.id.receives deltas1 parent · 0..* childrenjournal_lineentry_id + account_id [PK]deltaentry_sequenceencumbers funds1 parent · 0..* childrenwallet_holdid [PK]account_id [FK]amountinitiates operations1 parent · 0..* childrenwallet_operationid [PK]account_id [FK]fingerprintwallet_accountid [PK]currencyposted
journal_entry: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

journal_entry relationshipsjournal_entry 0..1 to journal_entry 0..1: is corrected by. journal_entry.reverses_id → journal_entry.id. journal_entry 1 to journal_line 0..*: contains deltas. journal_line.entry_id → journal_entry.id.is corrected by0..1 parent · 0..1 childrenjournal_entryid [PK]reverses_id [FK]operation_keycontains deltas1 parent · 0..* childrenjournal_lineentry_id + account_id [PK]deltaentry_sequencejournal_entryid [PK]reverses_id [FK]operation_key

Entity definitions and field contracts

wallet_account

One currency balance owner.

FieldType / keyMeaning and reference
idtext · PKAccount identity
currencytext · VALUEDeclared currency
postedbigint · VALUEDerived signed balance
reservedbigint · VALUEActive hold projection

Code mapping: Wallet balances and reserved maps.

  • Normal wallet posted>=reserved>=0; internal clearing has explicit signed exemption.

journal_entry

Immutable balanced operation.

FieldType / keyMeaning and reference
iduuid · PKPosting identity
reverses_iduuid? · FKOptional original posting · journal_entry.id
operation_keytext · UKLogical command identity
sequencebigint · UKMonotonic posting order for replay; all lines use the same entry sequence

Code mapping: Posting record.

  • Sum of entry line deltas is zero; one permitted reversal per original under reference policy.

journal_line

Account delta inside one posting.

FieldType / keyMeaning and reference
entry_iduuid · PK,FKBalanced entry · journal_entry.id
account_idtext · PK,FKAffected account · wallet_account.id
deltabigint · VALUEExact signed minor-unit change
entry_sequencebigint · VALUEImmutable posting sequence copied consistently from entry for account history scans

Code mapping: Posting deltas map.

  • PK(entry_id,account_id); validate full entry sum with overflow-safe arithmetic.

wallet_hold

Active encumbrance lifecycle.

FieldType / keyMeaning and reference
iduuid · PKHold identity
account_idtext · FKFunds owner · wallet_account.id
amountbigint · VALUEReserved amount
stateenum · VALUEACTIVE CAPTURED RELEASED

Code mapping: Hold record.

  • One terminal path wins; captured hold does not reactivate on posting reversal.

wallet_operation

Typed immutable retry evidence.

FieldType / keyMeaning and reference
idtext · PKOperation key
account_idtext · FKInitiator scope · wallet_account.id
fingerprintjson · VALUEOperation kind and exact inputs
result_idtext · VALUERecorded hold/posting identity

Code mapping: Operation fingerprints.

  • Global namespace rejects same key with different operation meaning.

Relationship cardinalities and their reasons

Parent → childMultiplicityFK / why
journal_entry → journal_entry

is corrected by

0..1 parent per child; 0..1 children per parentjournal_entry.reverses_id → journal_entry.id

Inverse entries append history; at most one reversal is an additional uniqueness constraint.

journal_entry → journal_line

contains deltas

1 parent per child; 0..* children per parentjournal_line.entry_id → journal_entry.id

An entry must have enough lines to express conservation; FK alone cannot enforce cross-row zero sum.

wallet_account → journal_line

receives deltas

1 parent per child; 0..* children per parentjournal_line.account_id → wallet_account.id

Many-to-many account/entry facts are resolved through immutable lines.

wallet_account → wallet_hold

encumbers funds

1 parent per child; 0..* children per parentwallet_hold.account_id → wallet_account.id

ACTIVE hold sum equals reserved projection.

wallet_account → wallet_operation

initiates operations

1 parent per child; 0..* children per parentwallet_operation.account_id → wallet_account.id

Retry identity protects both financial effects and encumbrance state.

Transaction boundaries

  1. Transfer: lock all affected accounts in stable order, resolve retry key/fingerprint, validate exact balanced deltas/availability, append entry/lines and update projection atomically.
  2. Capture: condition ACTIVE hold, remove its encumbrance, apply debit/credit and store operation result in the same transaction.
  3. Reversal: append inverse lines only if present available constraints permit; never edit old journal facts.

Indexes and uniqueness

  1. PK entry/account for replay; index journal_line(account_id,entry_sequence).
  2. Unique operation key; unique non-null reverses_id under one-reversal policy; index active wallet_hold(account_id).

Schema traps and failure cases

  1. A row CHECK cannot enforce cross-row posting sum; use a single transactional posting owner/deferred constraint mechanism.
  2. Balanced internal entries do not prove bank settlement. Unknown external transfer requires reconciliation evidence.

Trace the records through one operation

  1. Opening balances have an opposing synthetic clearing posting.
  2. Hold H reserves 40, reducing available but not posted balance.
  3. Capture H posts payer -40 and payee +40; racing release loses terminal transition.

Method contracts

Ledger(initialFunding)

Validate accounts/amounts and checked aggregate capital. Create balanced opening posting against @clearing.

transfer(from,to,amount,key) → Posting

Resolve matching key, validate wallets/positive amount, prepare balanced updates and enforce posted>=reserved before committing one entry.

reserve(wallet,amount,key) → Hold

Create an ACTIVE hold only within current available funds; no posting is added. Same key resolves that hold identity.

capture(holdId,to,key); release(holdId)

Capture releases reserved amount and posts its transfer together. Release only clears an ACTIVE reservation; repeats of RELEASED are no-ops.

reverse(postingId,key); balances(); rebuild()

Append one inverse subject to current funds. Rebuild includes opening and correction facts, preserving audit history.

What, when, why and how

Each concept is attached to the requirement it protects, its implementation, an alternative and a test.

Balanced postings and conservation

what
Every movement has equal and opposite account deltas.
when
Money-like value moves between wallets.
why
Independent debit/credit writes can create, lose or conceal value after partial failure.
how
Opening funding is offset by clearing; transfer/capture create zero-sum delta maps. nextBalances validates sum with BigInteger and computes checked next balances before commit.

Code: Posting, nextBalances, commit · Invariants: I1

Alternatives, cost and failure example

Alternative: A mature accounting model may distinguish account types and debit/credit conventions explicitly; this reference uses signed deltas for clarity.

Cost: The implementation copies M balances per posting and checks all wallet constraints, so update is O(M+P), not universally O(1).

Counterexample: Debit A succeeds, credit B fails and history still claims a completed transfer.

Test: Assert zero sum and rebuild equality after each seeded transfer and every race result.

Posted versus available balance

what
Reservations encumber funds without yet moving posted value.
when
A payment is authorized/reserved before final capture.
why
Using posted balance alone lets another transfer spend money already promised to a hold.
how
reserve increases reserved only. available subtracts it. transfer/reversal validate next posted balances against current reserves; capture lowers reserve and posts debit/credit atomically.

Code: reserve, available, capture · Invariants: I2, I3

Alternatives, cost and failure example

Alternative: A separate pending ledger can express similar semantics, but consistency between pending and posted state still needs an atomic boundary.

Cost: Long-lived abandoned holds reduce availability indefinitely here; expiry/recovery belongs to a separate explicit policy.

Counterexample: A has70 posted and50 reserved, but a transfer of21 is accepted by checking only70.

Test: Reserve50 after balance70 and reject transfer21; capture50 transfers it once while preserving available20.

Idempotent result and exclusive hold terminal state

what
Retries resolve the original request, while capture/release race for one active reservation.
when
Network retries or concurrent settlement/cancellation occur.
why
Duplicating a transfer or both capturing and releasing a hold violates value/availability guarantees.
how
Typed operation records compare exact business arguments. Capture checks ACTIVE after repeat resolution; release returns RELEASED idempotently but rejects CAPTURED. All share one monitor.

Code: Operation, capture, release · Invariants: I3, I4

Alternatives, cost and failure example

Alternative: Durable unique operation keys and conditional hold-state writes are needed for multiple service instances.

Cost: Result retention grows and must outlive the supported retry horizon.

Counterexample: Capture and release independently read ACTIVE, then both mutate reserved accounting.

Test: Barrier-controlled capture/release race yields one winner; matching capture retry returns the same Posting.

Reversal is a new constrained transaction

what
Correct a posted fact through an inverse rather than erasing history.
when
An accepted transfer needs business reversal.
why
The recipient may no longer have available funds, so compensation cannot be assumed infallible.
how
reverse negates original deltas, checks current reserves/funds, appends a linked Posting and records one reversal. Opening and reversal-of-reversal are excluded.

Code: reverse, rebuild · Invariants: I1, I2, I4

Alternatives, cost and failure example

Alternative: A business liability/manual-review account can represent an owed refund when direct reversal cannot succeed, but that is a different declared policy.

Cost: A failed reversal leaves the original fact intact and may require a real-world recovery process; this class does not invent funds.

Counterexample: Reverse capture from B while all B funds are reserved elsewhere.

Test: Reject without adding history, then release the encumbrance and append the inverse successfully.

Read and run the Java reference

Reveal complete Solution.java

Java 21. Save as Solution.java; compile with javac --release 21. The repository also contains Test.java and the lld:java verification command. This reference implements the local contract above.

Solution.java Java 21
import java.math.BigInteger;
import java.util.*;
/** One-currency ledger. Funding is supplied by constructor fixtures, not external banking rails. */
public final class Solution {
    private Solution() {}
    public enum HoldState { ACTIVE, CAPTURED, RELEASED }
    public record Posting(String id,Map<String,Long> deltas,String reverses){public Posting{deltas=Map.copyOf(deltas);}}
    public record Hold(String id,String account,long amount,HoldState state){}
    private record Transfer(String from,String to,long amount){}
    private record Reserve(String account,long amount){}
    private record Capture(String holdId,String to){}
    private record Reverse(String originalId){}
    private record Operation(Object fingerprint,String resultId){}
    public static final class Ledger {
        private static final String CLEARING="@clearing";
        private final Map<String,Long> balances=new TreeMap<>(),reserved=new TreeMap<>();
        private final Map<String,Posting> postings=new LinkedHashMap<>();
        private final Map<String,Hold> holds=new HashMap<>();
        private final Map<String,Operation> operations=new HashMap<>();
        private final Set<String> reversed=new HashSet<>();
        private long sequence;
        public Ledger(Map<String,Long> funding){
            if(funding.isEmpty()||funding.keySet().stream().anyMatch(id->id==null||id.isBlank()||id.equals(CLEARING))||funding.values().stream().anyMatch(v->v==null||v<0))throw new IllegalArgumentException("funding");
            long total=funding.values().stream().map(BigInteger::valueOf).reduce(BigInteger.ZERO,BigInteger::add).longValueExact();balances.putAll(funding);balances.put(CLEARING,-total);funding.keySet().forEach(k->reserved.put(k,0L));var initial=new TreeMap<>(funding);initial.put(CLEARING,-total);postings.put("opening",new Posting("opening",initial,""));
        }
        private void account(String id){if(!reserved.containsKey(id))throw new IllegalArgumentException("wallet account");}
        private static void key(String key){if(key==null||key.isBlank())throw new IllegalArgumentException("key");}
        private Operation previous(String key,Object fingerprint){key(key);var old=operations.get(key);if(old!=null&&!old.fingerprint().equals(fingerprint))throw new IllegalArgumentException("key reused");return old;}
        private Map<String,Long> nextBalances(Map<String,Long> delta,Map<String,Long> required){
            if(delta.values().stream().map(BigInteger::valueOf).reduce(BigInteger.ZERO,BigInteger::add).signum()!=0)throw new IllegalArgumentException("unbalanced");
            var next=new TreeMap<>(balances);delta.forEach((a,d)->{account(a);next.put(a,Math.addExact(next.get(a),d));});
            for(String a:reserved.keySet())if(next.get(a)<required.get(a))throw new IllegalStateException("insufficient available funds");return next;
        }
        private Posting commit(Map<String,Long> delta,Map<String,Long> next,String reverse){var posting=new Posting("P"+(++sequence),delta,reverse);balances.clear();balances.putAll(next);postings.put(posting.id(),posting);return posting;}
        public synchronized Posting transfer(String from,String to,long amount,String key){account(from);account(to);if(from.equals(to)||amount<1)throw new IllegalArgumentException("transfer");var fp=new Transfer(from,to,amount);var old=previous(key,fp);if(old!=null)return postings.get(old.resultId());var delta=Map.of(from,-amount,to,amount);var next=nextBalances(delta,reserved);var p=commit(delta,next,"");operations.put(key,new Operation(fp,p.id()));return p;}
        public synchronized Hold reserve(String account,long amount,String key){account(account);if(amount<1)throw new IllegalArgumentException("amount");var fp=new Reserve(account,amount);var old=previous(key,fp);if(old!=null)return holds.get(old.resultId());long next=Math.addExact(reserved.get(account),amount);if(next>balances.get(account))throw new IllegalStateException("insufficient available funds");var h=new Hold("H"+(++sequence),account,amount,HoldState.ACTIVE);holds.put(h.id(),h);reserved.put(account,next);operations.put(key,new Operation(fp,h.id()));return h;}
        public synchronized Posting capture(String holdId,String to,String key){account(to);var h=hold(holdId);if(h.account().equals(to))throw new IllegalArgumentException("recipient");var fp=new Capture(holdId,to);var old=previous(key,fp);if(old!=null)return postings.get(old.resultId());if(h.state()!=HoldState.ACTIVE)throw new IllegalStateException("hold not active");var remaining=new TreeMap<>(reserved);remaining.put(h.account(),reserved.get(h.account())-h.amount());var delta=Map.of(h.account(),-h.amount(),to,h.amount());var next=nextBalances(delta,remaining);var p=commit(delta,next,"");reserved.clear();reserved.putAll(remaining);holds.put(h.id(),new Hold(h.id(),h.account(),h.amount(),HoldState.CAPTURED));operations.put(key,new Operation(fp,p.id()));return p;}
        public synchronized Hold release(String holdId){var h=hold(holdId);if(h.state()==HoldState.RELEASED)return h;if(h.state()!=HoldState.ACTIVE)throw new IllegalStateException("already captured");reserved.put(h.account(),reserved.get(h.account())-h.amount());var next=new Hold(h.id(),h.account(),h.amount(),HoldState.RELEASED);holds.put(h.id(),next);return next;}
        public synchronized Posting reverse(String postingId,String key){var p=postings.get(postingId);if(p==null||postingId.equals("opening")||!p.reverses().isEmpty())throw new IllegalArgumentException("posting");var fp=new Reverse(postingId);var old=previous(key,fp);if(old!=null)return postings.get(old.resultId());if(reversed.contains(postingId))throw new IllegalStateException("already reversed");var delta=new TreeMap<String,Long>();p.deltas().forEach((a,v)->delta.put(a,Math.negateExact(v)));var next=nextBalances(delta,reserved);var result=commit(delta,next,postingId);reversed.add(postingId);operations.put(key,new Operation(fp,result.id()));return result;}
        public synchronized Hold hold(String id){var h=holds.get(id);if(h==null)throw new IllegalArgumentException("hold");return h;}
        public synchronized long available(String account){account(account);return balances.get(account)-reserved.get(account);}
        public synchronized Map<String,Long> balances(){return Map.copyOf(balances);}
        public synchronized List<Posting> postings(){return List.copyOf(postings.values());}
        public synchronized Map<String,Long> rebuild(){var result=new TreeMap<String,Long>();balances.keySet().forEach(a->result.put(a,0L));for(var p:postings.values())p.deltas().forEach((a,v)->result.put(a,Math.addExact(result.get(a),v)));return Map.copyOf(result);}
    }
}

nextBalances validates conservation and availability

BigInteger sum avoids overflow in the validation itself. Checked long deltas are prepared before updating authoritative state.

capture uses post-release reserved requirements

The hold amount is removed from the temporary reserve map before validating the debit, so reserved funds can be consumed by their own capture without double encumbrance.

previous resolves typed retry meaning

One key cannot become a different operation or change amount/recipient. Result lookup happens before terminal hold checks on repeated capture.

reverse does not rewrite hold history

A captured hold remains CAPTURED after its posting is reversed. The inverse changes money facts; it does not silently create a new active authorization.

Complexity and limits

  • Posting with M wallets and P deltas copies/checks O(M+P) state; map lookup is expected O(1). Reserve/release are expected O(1) apart from stored record copies.
  • Capture copies reserved/balance maps and validates all wallets: O(M). Rebuild costs O(M+total retained delta entries).
  • Space grows with wallets, postings, holds and operation/reversal history. Queue or hold counts do not bound byte retention automatically.
  • One monitor avoids local account-lock deadlocks but serializes unrelated transfers. It gives no cross-process durability, real payment settlement or regulatory guarantees.

Walk through the actual reference

These walkthrough fixtures are emitted only after the Java harness verifies their operations and expected state. Concurrency tests additionally exercise competing callers.

Posted and available funds

Step 1 of 3: Fund A100, transfer30 to B

A70/B30, clearing-100

Invariant: All posting deltas sum to zero

Read the complete trace table
ActionState after actionInvariant
Fund A100, transfer30 to BA70/B30, clearing-100All posting deltas sum to zero
Reserve50 from AA posted70, available20Reserved funds cannot be spent again
Capture hold to BA20/B80, hold CAPTUREDReservation release and debit/credit commit together

Reversal can fail when funds are encumbered

Step 1 of 2: Reserve all B80 then reverse capture

Rejected; posting history unchanged

Invariant: Correction cannot violate active reservations

Read the complete trace table
ActionState after actionInvariant
Reserve all B80 then reverse captureRejected; posting history unchangedCorrection cannot violate active reservations
Release B hold and reverseAppend inverse; A70/B30Original posting remains auditable

Mistakes and counterexamples

Spend posted balance without subtracting holds

Failing case: A second transfer consumes already reserved funds.

Repair: Validate available funds and atomically coordinate capture/release.

Update debit and credit separately

Failing case: A failure occurs between them.

Repair: Commit balanced postings and projection together.

Assume reversal always succeeds

Failing case: Recipient has spent or encumbered the money.

Repair: Enforce current constraints and track business recovery explicitly.

Erase original transactions during correction

Failing case: Audit/rebuild cannot explain a disputed transfer.

Repair: Append an inverse with a stable original reference.

Engineer → Senior → Staff

Choose a deep dive

Implement exact transfers and balance evidence.

Executed: opening conservation, transfers, exact amounts and deterministic projection rebuild.

Change exercise

Add a fee as a balanced multi-account posting rather than hidden subtraction.

Worked answer guidance

Include a fee recipient/account and preserve zero sum. Validate every resulting available balance atomically and keep the fee rule/version in operation identity.

Interview follow-ups

Why is clearing negative?

It offsets synthetic opening funding in this simplified signed-delta model. It is not a normal wallet that callers may spend.

Why use BigInteger for sum validation?

Even if each amount fits long, summing several deltas can overflow a long before detecting imbalance.

Your design attempt

Each language has a separate draft across all three levels. Saving is device-only and does not sync to your account. Anyone using this browser profile can load it. Loading is always explicit.

Java 21 reference code is provided for local study. Use Run program in the editor to execute your code and test driver. Program runs show output; they do not grade your design.

Solution.java
Run program · Java 21

No unsaved changes · 208/20000

Self-review

Checks apply to this visit. Self-review is not a passed submission or verified mastery.

Open focused practice →

Provenance and scope of evidence

Original teaching material. The local contract is implemented by Solution.java and tested by Test.java on Java 21. Distributed persistence, real provider/device adapters and deployment are not claimed.

Reference verification is not independent editorial review, production deployment verification or a claim that distributed adapters are implemented.