Never store a balance
Why every wallet I build now uses a double-entry ledger, and the five lines of code that make it work.
By Emmanuel Olowu
The first wallet I built stored the balance as a number on the user row. It worked until two requests updated it at the same time, and a customer was briefly richer than they should have been.
Balances are a query, not a column
In a double-entry ledger, every movement of money writes two entries that sum to zero. A balance is never stored. It is calculated from the entries.
select coalesce(sum(amount), 0) as balance
from ledger_entries
where account_id = $1;That one decision removes a whole class of bugs. You cannot forget to update the balance, because there is nothing to update.
What you get for free
- An audit trail. Every change has a reference, a time and a counterpart.
- Reconciliation. Totals per account should match the payment provider. When they do not, you know exactly which day to look at.
- Safe retries. Make the reference unique and a replayed webhook simply fails to insert.
When it is overkill
If nothing in your product holds value between transactions, you do not need this. The moment users can top up, hold or withdraw, you do.