Double-entry bookkeeping has survived 500 years because it’s essentially a distributed consensus mechanism. Every transaction is a commit that must balance. No partial states. No eventual consistency. Just maths.
If you’ve ever built a database with foreign key constraints, you already understand the principle. Double-entry is a constraint: every pound that enters one account must leave another. The books always balance, or the system rejects the transaction.
The Core Rule
Every financial event records at least two entries: a debit and a credit. The total debits must equal the total credits. Always.
Total Debits = Total Credits
That’s it. That’s the invariant. Everything else is implementation detail.
Debits and Credits Are Not Good and Bad
This is where most explanations lose developers. “Debit” doesn’t mean “money out” and “credit” doesn’t mean “money in.” They’re directions, not judgements.
Think of it like this: every account has a normal balance — the side that makes it increase.
| Account Type | Normal Balance | Increases With | Decreases With |
|---|---|---|---|
| Asset | Debit | Debit | Credit |
| Liability | Credit | Credit | Debit |
| Equity | Credit | Credit | Debit |
| Revenue | Credit | Credit | Debit |
| Expense | Debit | Debit | Credit |
Your bank account is an asset. When you receive payment, the bank balance goes up — that’s a debit to the bank account. When you pay for hosting, the bank balance goes down — that’s a credit to the bank account.
The hosting cost is an expense. Expenses increase with debits. So the same transaction debits the hosting expense account.
Two entries. Equal and opposite. The books balance.
A Real Example
A client pays your £5,000 invoice:
DEBIT Bank Account (Asset) £5,000 ← Bank balance goes up
CREDIT Revenue (Income) £5,000 ← Revenue goes up
Both sides equal £5,000. The transaction is valid.
Now you pay £89 for AWS hosting:
DEBIT Hosting (Expense) £89 ← Expense goes up
CREDIT Bank Account (Asset) £89 ← Bank balance goes down
Again, both sides balance.
The Developer Analogy: Git Commits
If you use git, you already think this way:
| Git Concept | Accounting Equivalent |
|---|---|
| Repository | General Ledger |
| Commit | Transaction (journal entry) |
| Diff | Debit/Credit entries |
git log | Transaction history |
git bisect | Audit trail |
| Merge conflict | Unbalanced transaction |
A git commit that only modifies one file is suspicious. A transaction that only has one entry is invalid. Both systems enforce consistency through structure.
What This Looks Like in an API
Here’s how SpeyBooks represents a double-entry transaction:
{
"success": true,
"data": {
"id": "txn_891",
"date": "2026-02-01",
"description": "Client payment - February consultancy",
"lines": [
{
"id": "line_203",
"accountId": "acc_1200",
"accountName": "Bank Account",
"debit": 500000,
"credit": 0
},
{
"id": "line_204",
"accountId": "acc_4000",
"accountName": "Consultancy Revenue",
"debit": 0,
"credit": 500000
}
],
"totalDebit": 500000,
"totalCredit": 500000
}
}
Notice the amounts are integers — 500000 means £5,000.00. We store everything in pence to avoid floating-point errors. More on that in Why We Store Money in Pence.
The API enforces the invariant at the database level. If totalDebit !== totalCredit, the transaction is rejected. No partial writes. No cleanup jobs.
The Accounting Equation
Every balance sheet in the world satisfies one equation:
Assets = Liabilities + Equity
Revenue increases equity. Expenses decrease it. Expand it out:
Assets + Expenses = Liabilities + Equity + Revenue
Both sides of this equation are always equal. That’s what “the books balance” means. Double-entry bookkeeping enforces this structurally — not through reconciliation, not through batch jobs, but through the fundamental design of every transaction.
Why You Should Care
If you’re building anything that handles money — invoicing, billing, payments, subscriptions — you need double-entry. Not because accountants say so, but because single-entry systems break.
Single-entry is like writing code without types. It works until it doesn’t, and when it fails, you can’t trace what went wrong. Double-entry gives you an audit trail baked into the data structure itself.
Every transaction tells you: where the money came from, where it went, and when. That’s not just accounting — that’s good systems design.
- Every transaction has at least two entries: a debit and a credit
- Total debits must always equal total credits — this is the invariant
- Debits and credits are directions, not good/bad — their effect depends on the account type
- Double-entry is a constraint system, like foreign keys or type safety
- The accounting equation (Assets = Liabilities + Equity) is always satisfied