Skip to content
Double-Entry Ledger Design: The 500-Year-Old System That Powers Banking 2 min

Double-Entry Ledger Design: The 500-Year-Old System That Powers Banking

SD
ScaleDojo
May 11, 2026
2 min read539 words
Double-Entry Ledger Design: The 500-Year-Old System That Powers Banking

Why Stripe Cannot Use a Simple Balance Column

Imagine storing user balances as a single column: UPDATE accounts SET balance = balance - 100 WHERE id = 42. Now imagine a race condition updates it twice. Or a bug subtracts without adding. Or an audit asks 'where did $50,000 go last Tuesday?' You have no history, no trail, no proof. Every fintech company from Stripe to Square to Robinhood uses double-entry ledgers instead - a system invented by Luca Pacioli in 1494 that makes financial errors mathematically impossible.

The Core Rule: Every Transaction Balances

Double-Entry: Every Transaction Creates TWO Entries

  Alice sends $100 to Bob:

  +------------------+--------+---------+
  | Account          | Debit  | Credit  |
  +------------------+--------+---------+
  | Alice's Wallet   | $100   |         |  <- money leaves
  | Bob's Wallet     |        | $100    |  <- money arrives
  +------------------+--------+---------+
  | TOTAL            | $100   | $100    |  <- MUST balance
  +------------------+--------+---------+

  The invariant: SUM(all debits) = SUM(all credits)
  If they do not balance, something is WRONG.
  You will discover it immediately, not months later.

  Alice's balance = SUM(credits) - SUM(debits) = -$100
  Bob's balance   = SUM(credits) - SUM(debits) = +$100
  System total    = $0 (money was moved, not created)

The Schema: Append-Only Journal

-- Accounts (the buckets money lives in)
CREATE TABLE accounts (
    id          UUID PRIMARY KEY,
    name        VARCHAR(255) NOT NULL,
    type        VARCHAR(20) NOT NULL,  -- ASSET, LIABILITY, REVENUE, EXPENSE
    currency    CHAR(3) NOT NULL DEFAULT 'USD',
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Journal entries (IMMUTABLE - never update, never delete)
CREATE TABLE journal_entries (
    id              UUID PRIMARY KEY,
    transaction_id  UUID NOT NULL,   -- groups debit + credit together
    account_id      UUID NOT NULL REFERENCES accounts(id),
    amount          BIGINT NOT NULL, -- cents, not dollars (avoid floats!)
    direction       VARCHAR(6) NOT NULL CHECK (direction IN ('DEBIT', 'CREDIT')),
    description     TEXT,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Balance is DERIVED, never stored:
-- SELECT SUM(CASE WHEN direction = 'CREDIT' THEN amount ELSE -amount END)
-- FROM journal_entries WHERE account_id = ?;

Why Immutability Is Non-Negotiable

Correcting Errors (the right way):

  Original charge (wrong amount):
  Entry #1001: Debit Customer $150, Credit Merchant $150

  DO NOT: UPDATE journal_entries SET amount = 100 WHERE id = 1001
  DO NOT: DELETE FROM journal_entries WHERE id = 1001

  INSTEAD: Create a REVERSAL entry:
  Entry #1002: Credit Customer $150, Debit Merchant $150  (undo)
  Entry #1003: Debit Customer $100, Credit Merchant $100  (correct)

  Result: Full audit trail preserved
  Auditor can see: original charge, reversal, correction
  Timestamps prove when each action happened
  No data was destroyed

Account Types in Practice

Account Types for a Payment Platform (like Stripe):

  Type         Examples                 Increases With
  ----------   ----------------------   ----------------
  ASSET        Cash, Bank Account,      DEBIT
               Accounts Receivable

  LIABILITY    Customer deposits,       CREDIT
               Accounts Payable,
               Platform float

  REVENUE      Processing fees,         CREDIT
               Subscription income

  EXPENSE      Refund costs,            DEBIT
               Infrastructure costs

  Stripe example: When you pay a merchant $100 with 2.9% fee:
  Debit:  Customer bank      $100.00  (asset decreases)
  Credit: Merchant account   $97.10   (liability increases)
  Credit: Stripe revenue     $2.90    (revenue increases)

Interview Tip

When designing a financial system, say: 'I would use a double-entry ledger where every transaction creates balanced debit and credit journal entries. Entries are immutable and append-only - errors are corrected with reversal entries, never updates or deletes. Account balances are derived by summing the journal (SUM of credits minus SUM of debits), never stored directly. Amounts are stored as integers in the smallest currency unit (cents) to avoid floating-point errors. This gives us a complete, auditable, tamper-evident financial history.'

<
Double-Entry Ledger Design: The 500-Year-Old System That Powers Banking - Architecture Diagram
Architecture overview
/blockquote>

Key Takeaway

Double-entry ledgers make financial inconsistencies mathematically impossible - every transaction must balance. Journal entries are immutable records. Account balances are derived by summing history, not stored directly. This 500-year-old system is still the gold standard for financial system correctness.

Enjoyed this article?

Share it with your network to help others level up their system design skills.

Discussion0

Join the Discussion

Sign in to leave comments, reply to others, or like insights.

Sign In to ScaleDojo

No comments yet. Be the first to start the thread!

Related Articles

Enjoyed this content?

Your support keeps us creating free resources

We put a lot of hours into researching and writing these guides. If it helped you, consider buying us a coffee. Every bit goes toward keeping ScaleDojo's content free and growing.

$

One-time payment via Stripe. ScaleDojo account required.

ScaleDojo Logo
Initializing ScaleDojo