The fastest way to sink a money app is to store a balance in a column and update it. It feels obvious, it is what a spreadsheet does, and it is a trap. The moment two requests touch that number at once, or a write half-fails and leaves it stale, or someone asks why the balance is what it is, you are holding a number you cannot defend and cannot rebuild. Picture the simplest version: two requests read the balance as 100 at the same instant, each adds 10, each writes 110, and the second silently erases the first. It hides completely with ten users and surfaces as a corruption incident with ten thousand, and no single-threaded test ever catches it.
Here is the plain version. A balance is not a thing you store. It is a thing you derive. You record every movement of money as an entry in an append-only log you never edit, and the balance is what you get by adding those entries up. Done this way the balance is always provable, because it is not a claim sitting in a column, it is the sum of everything that ever happened. The answer to this problem is not new. It is double-entry bookkeeping, written down by a Venetian friar in 1494, and it is still the only model that survives a real reconciliation.
Here is that race, run honestly. Same two deposits, two architectures:
The invariants are constraints, not good intentions
Double-entry says every movement is two entries that have to balance: for any transaction, the debits equal the credits. Around that sit a few more rules that are not negotiable. A balance is the sum of an account's postings, never a separate stored figure. No posting points at an account that does not exist. Every amount carries an explicit currency, because a number without a currency is not money, it is a guess. And money is stored as whole integer units of the smallest denomination, cents and not dollars, never a floating-point number, because a float cannot hold ten cents exactly and an app that loses a fraction of a cent per transaction is quietly wrong a million times a day.
The part that matters is where those rules live. They belong in the database as constraints, checked at commit, not in application code as things you remember to do. A multi-row transaction can insert its legs one at a time and still be validated as a whole the instant it commits, so an unbalanced transaction simply cannot land. When the rule is a property of the table, there is no code path, not a careless one and not an automated one, that can get around it. That is the difference between a ledger that is correct and a ledger that is correct so far.
You correct a ledger by adding to it
The other half of immutability is what you do when something is wrong. You do not edit the original postings and you do not delete them. A reversal is a new, fully balanced transaction that negates the original, so the record of what happened, including the mistake and the fix, stays intact. This is what lets the ledger double as the audit trail, and it is the thing a generic balances table can never give you: the ability to answer not just what the balance is, but every step of how it got there. Apply a transaction, then reverse it, and every affected balance returns to exactly where it started, to the cent. If your system cannot promise that, it is not a ledger yet.
We see the cost of skipping this constantly now. A non-technical founder builds an accounting product with an AI assistant, it demos beautifully, and then a senior engineer gets called in because the journal entries do not hold, the double-entry invariant was never enforced, and nobody can explain why a balance moved. The same lending platforms we have built run on this exact discipline, where every apply, approve, disburse, and repay is a state transition that has to leave the books balanced, because a loan ledger that drifts is not a bug ticket, it is a dispute with a customer about their money. The reconciliation that proves the ledger agrees with the processor and the bank is the next piece.
What's still standing in 2028
This is the layer AI cannot fake, and that is exactly why it matters more as everything above it gets cheaper. A model will generate a balances table and an update statement in seconds. What it will not generate, unless someone makes it, is the constraint that refuses an unbalanced transaction at commit time. Correctness here is a property of the schema, not of the code that happened to write it, which means the ledger is one of the few places in a fintech where the moat is structural and the thin wrapper has nothing to copy.
What 2muchcoffee covers
We build and harden the ledger layer underneath money-moving products, the double-entry model, the invariants as constraints, the append-only history that an auditor can actually read. If you are about to put a balance in a column because it is faster, that is the moment worth a conversation, before the first race condition turns it into a number nobody can defend. The plain way in is the AI and engineering work we do.
One concrete action
Find every place your system stores a balance as a value you write. For each one, ask whether you could rebuild that number from a log of movements if the column were deleted tomorrow. Wherever the answer is no, you have found the part of building fintech software to fix first, because every other guarantee in the system is sitting on top of it.