Accounts are Evil

A Time Long Past

Accounts and double-entry ledgers were designed for a time when people wrote individual transactions down in a book as they processed them. At the end of the day a human would sum up all the transactions to figure out the new balances. If individual tellers had allowed accounts to be overdrawn, you'd have to figure out what accounts were negative and contact the holders. Hopefully, you could get your money back. The bigger issue (usually) was that there was a significant chance that the human summing the transactions to create the new balances could do the math wrong. What was needed was very simple mathematical checks that could be used to figure out when a person had done their math wrong.

It made sense at the time: you can't expect a human to follow individual pieces of money through your system. It was also silly to assume an employee could check every other teller's ledger for a real time account balance when they made a transaction in order prevent overdraws or errors. It was also expected that occasionally human error would result in bad transactions or balances. The processes they designed were targeted at the problems they had at the time.

This doesn't happen in our time though. We have computers. They never make math mistakes. They have the computational power to keep track of billions of individual items and their contexts. They can do real time checks and prevent financial (or business logic) errors. So why are we still using constructs that were built for humans writing on paper?

Accounts suck for computers. Building a financial system on top of them is a terrible idea. It will cost your company an enormous amount of money in operational costs, data pipelines, and in agility. You'll find it's difficult to answer what should be simple questions about your systems without having to constantly rewire large swathes of your code. The problems are so systemic and so unrecognized that they are written off as "the cost of doing business" or simply "how things are". It's normal for organizations to have large operational staff and accounting functions focused entirely on trying to make sense of accounts, and reorganize them every couple years.

It doesn't need to be this way. We created this problem by applying human restrictions to computers. We can do better.

My goal is to walk you through some of the many difficult problems you'll find building a financial system on top of accounts, and ultimately suggest a better way forward.

The Case for Accounts

If you ask companies how they are managing money on their platforms, almost universally you'll hear the refrain "We use accounts and double-entry style transactions". Similarly, if you put some Google work into finding suggestions for how to build a financial system (it will be work, it's not often written about) you will find something like the Books route taken by Square. In fact, you'll be hard pressed to find anyone suggesting that you shouldn't build with accounts, balances, and double-entry ledgers.

But if accounts are such an insidious and evil building block, why are they so often used?

It isn't just inertia that keeps everyone building on accounts. There are several very straight forward reasons people choose this pattern so let's address them.

Accounts

We'll talk about balances separately but for accounts we'll focus on two of their most lauded attributes: their flexibility and their familiarity.

Flexibility

An account can be as simple as a name with an attached balance. You don't even have to plan its creation ahead of time you can create it on the fly when a transaction comes in for an account with the name. You don't need a bunch of account structure design work, you just let your accounts system mature organically over time based on your needs. Perfect. Flexible. A lie.

The first fallacy you'll hit with this (and you'll find it quickly) is that accounts can't be as simple as a name. The Single Context Problem says that a balance is only described by its account, so to know more about your balances you need more descriptive account. You'll quickly have structured data stored as string tokens in the name of the accounts like: "green apples picked less than 24 days ago", "seller 1234 eu eur VAT", or "us gift card escheatmeant texas entity". There is meaningful, queryable data inside these strings, but its going to be really hard to search for relevant accounts. Those balances have meaning, and they need to be described but how do you structure that data while keeping your account system flexible? The big idea I'll describe in Non-fungible Money is to structure the data that actually has to do with the money itself, and keep the rest-unstructured.

In order to have more detailed data you'll be breaking down accounts into smaller, more fined grained ones. If accounts are just named buckets you won't have any way of connecting or aggregating the small accounts in your system. This will lead to very difficult to understand structures as your account design matures.

While the concept of accounts are flexible (as small as just a name or id) that flexibility also makes them almost meaningless on their own. What does the balance actually represent? How is it related to other accounts? Can it's balance go negative? Who owns the money inside the account? What is the balance comprised of?

You'll need structured data with specific constraints to answer these questions, and accounts themselves offer nothing. Everything is up to you, and if you get it wrong, or discover a new dimension you want to understand later, you will be stuck rewiring large parts of your system thanks to The Workflow Problem. So while the flexibility is can be useful, its so flexible that it really doesn't provide any minimum structure or constraints for running a financial system either. You are expected to build that on top of the accounts, and its easy to make mistakes.

Familiarity

Ah, we get to the easy part. Why use accounts? We all know what they are! You probably have a bank account, credit card account, maybe an escrow account for your house or maybe a brokerage account for your investments. No one needs to explain accounts to you. You know them. Easy!

But... how much do you really know about how your accounts work?

The problem here stems from the flexibility of the account concept itself. Consumers make lots of assumptions about how accounts should function, but how they behave actually varies a lot based on context (examples below). When building a financial system you'll also have to balance how your consumer will think about the money with how your business will think about it, which are often opposite perspectives.

Bank Account

Bank accounts are very familiar (particularly in the US) and you probably think you know how they work, but let's dig a bit deeper using a standard checking account.

You log into your bank and see something like this: Checking Account Balance

Ok, wow. Great! My available balance is $1,000.89. Got it. If I go to a bank and ask for that much money they'll give it to me. Got it!

What is this other, smaller font number that says present balance though? How is that different from an available balance?

Banks use different terms for this, but the present balance includes unsettled transactions. Money either coming into your account that that isn't yet part of your balance, or money leaving your account that is no longer usable, but hasn't actually been withdrawn yet. Remember, The Single Context Problem says that you can't break down the balance of a single account, so how does the bank know the about these pending balances? The answer is almost certainly that your "checking" account is actually 3 separate accounts: pending deposit, available, and pending withdraw. What I think is my account, is actually a aggregation of several different accounts being displayed. That's a little bit of abstraction but not too bad.

Now let's switch to the bank's view. What do these accounts mean to them?

First off, the available balance that you view as an asset you can withdraw is a liability to the bank. It's money that they owe to you. That probably makes sense. We all expect that we can withdraw money that we put into a bank.

However, the bank doesn't actually have your money. They loaned it to another bank, or invested it the second you put it in there. US banks are only required to keep 10% of the balance of their accounts in reserve (not lent out, or invested) and most don't keep anymore than that. Some of that balance you see also represents money that's available for the bank to loan out.

From the bank's perspective (who built the account system), is your balance a positive number or a negative number? They owe you the money, feels like it could be negative. However, they can also lend out that money and that money should be positive since its available. It's your money though (or at least they owe you someone's money) so you want to see it as positive too. So, which one is right?

This highlights to one of the fallacies touted by double-entry systems The Single Number Fallacy which says that every account has a single number that represents its balance. The customer should have a positive balance for the money their owed, the bank should have a negative balance for money they owe the customer, and the bank should have a positive balance for how much they have available to lend... but all three of these balances are actually looking at the exact same money in different ways. We have three balances, but not 3x the money so which of these is "the single number"?

Do you pick negative balance, and try to make sure every time its displayed to a user its positive? What about how much of the money is currently lent out? Do you split the account into reserve and invested accounts, then make sure to always combine them for consumers?

All of these approaches are right, and they are also all wrong. They are each valid perspectives for understanding the money tracked by your system, and they are all incompatible with each other. No matter which of these approaches (or any other) your bank choose when making their system it does not match with the intuitive perspectives from the other users.

Credit Accounts

Next let's explorer an equally common example: credit accounts. In this case we'll use a credit card. You log into your credit card site and you see a balance like this:

Credit Account Summary

What does the total balance number mean? Instead of showing how much money the bank owes you, this balance shows how much you owe the credit card company.

It is an account.

It has a balance.

It has a positive balance, just like the bank account.

The balances opposite meanings.

Think about that for a minute. You have two things called a accounts. Two numbers called balances. Two positive numbers. One is how much a bank owes you, the other is how much you owe a credit card company. Whether your intuition was that a balance is your money, or money that you owe, each would be valid even though those are complete opposites.

There are many other examples we could go through, but there are three major lessons about familiarity from these:

  1. Consumer intuition is usually based on abstraction. They often don't know how many accounts they actually have, or understand any nuance of the composition.
  2. Understanding of account is highly contextual. A positive balance doesn't even mean the same thing from account to account.
  3. Their are often competing perspectives of the same money within a system.

These properties all have one distinct side-effect: no matter how you choose to build your accounts they will violate user expectations. There are multiple valid perspectives in every system and you will absolute foul it up for someone no matter which you choose. That means there is no familiarity. You will have to build the entire context for the user yourself, including meanings and expected behavior. You get less than nothing from accounts, since many consumers won't bother to think about the fact that their perspective may not be the only one and believe their intuitive understanding is how the system works.

Balances

Accounts have balances. We mostly know this intuitively from using other accounts in our daily lives. This is actually a big draw for many looking to implement financial systems. People and systems will want to know balances so having a number saved in a database you can fetch will simply things a lot... in theory.

Balances are pre-computed

As transactions come into the system you update the account balances. So when you need to fetch a balance for an account there is no fancy querying needed. Find the account, request the balance. Operational complexity of O(1). Elegant. Easy.

Unfortunately this ideal breaks down pretty quickly as your account system matures. While individual accounts will still have single number, pre-computed balances most of your logic will actually require aggregations of many accounts: see The Single Context Problem and The Single Number Fallacy for why this happens.

Balances are authoritative

We have a number, saved in a database for each of our accounts. Now whenever we need to make a decision about money like "Should we pay this person?" or "Can the customer withdraw this much money?" we can simply look at the balance. The customer wants $400, but their balance is $300? DENIED! A seller wants to know why they haven't been paid, and their balance is -$30? YOU PAY ME!

This would be a neat property, if it held, but breaks down for two reasons. First is that individual account balances become less useful as a system matures (The Single Number Fallacy). The second is that using a double entry system balances need to be able to go negative, but that doesn't mean owe you money. I call this The Debt Problem and it will require you to add extra constraints in your system (or model debt differently) in order to reliably make decisions based on balance.

Balances can be carried forward

This will be intuitive to people familiar with accounting, but less obvious if you are starting from a programming background and working to build a financial system. The idea here is that since balances are authoritative you can take a point in time balance, call it a new starting point, and archive all the transactions that happened before it. As an example, let's say this is our monthly activity for an account:

Line# Credits Debits Balance
1 0 0 0
2 +3 0 3
3 +6 0 9
4 0 -4 5

Now we're moving on to the next month we can carry the balance forward and start cleanly by archiving the old transactions

Line# Credits Debits Balance
1 0 0 5

There is no rebuttal here. It's a legitimately good property of this approach. In accounting this is usually done a monthly basis, but as a financial system you will likely only do it on a yearly basis, or perhaps even longer depending on volume or user expectations.

Double Entry Transactions

If you aren't familiar with double entry style accounting the way that it works is each movement of money must be two-sided with one side being a credit (increases a balance) and the other a debit (reduces a balance). This has some really cool properties that help make a it easy to check the soundness of a system through basic summing. This was critically important when humans were manually balancing accounts.

Everything sums to 0

When each transaction has a positive credit side, and a negative debit side, it means that each transaction must sum to exactly 0. Let's look at an example

Line # Payments Escrow Seller Total
1 -30 + 30 0 0
2 0 -30 + 30 0
3 -10 + 10 0 0
- - - - -
Balance -40 +10 +30 0

Sum of balances:
-40 + 10 + 30 = 0

Using this property as long as each transaction (Lines 1-3) individually sums to zero we know that no money was lost by the system. You can also see that as long as all the transactions sum to zero, the sum of all of the balances will also be zero! This is a very easy check that just requires summing and ensures system integrity.

This is legitimately a useful property of accounts and double entry. However, the level of security it gives you is often vastly overestimated. The system summing to zero only ensures that no unbalanced transactions were recorded, which is an important but incredibly low bar. Your system can still massively misbehave while this property holds true. This is covered in detail in The Zero Sum Fallacy but let's give one quick example. Our payments account (which represents money coming into our system) is an increasingly and infinitely negative account. What if we have a transaction like this:

Line # Payments Escrow Seller Total
1 -1,000,000,000 +1,000,000,000 0 0

Its pretty unlikely that my business actually received a single $1 billion dollar payment, but does anything stop this? There is no balance on the payment account we could check to see if this should be allowed - it's unboundedly negative. Similarly your outbound accounts (Seller, in this case) need to be unboundedly positive which makes for similar problems. This problem doesn't only exist at the edges of your system though thanks to The Debt Problem it can happen even with internal transfers.

The overall problem here is that while treating everything as a double sided transaction makes for some convenient math it doesn't actually give you much (if any) financial integrity. The basic underlying check that an internal transaction should be balanced is a good one, but is far, far too basic to ensure a properly running business.

Transactions are atomic

Double entry transactions are atomic by definition: meaning either both sides get saved, or nothing does. This attracts many programmers looking for financial solutions. Having a minimum atomic unit of work makes for easy DB planning. If you need to shard data, you can just replicate the transaction rows everywhere and dead-simple multi-region support.

First off: multi-region support is never dead simple. Anyone who is telling you that is selling you something, and what they are selling doesn't work. Please don't buy that hype, especially for a financial system. The trade offs are many, complex, and the right answer varies from company to company based on their business model and appetite for risk.

The atomicity part is true. If you build a system where the base unit is double entry transactions (instead of accounts/balances) you can get that benefit. However, being truly transaction based means that you must have eventually consistent balances (since only the transactions themselves are atomic). Eventually consistent balances aren't necessarily a bad thing. If the system you are building is only designed to record what other systems have already done (often call after-the-fact recording) this works quite well since you aren't making financial decisions based on the balances. However, you'll need a plan for computing those balances through data pipelines, eventing, or some other asynchronous processing method.

You'll still need some sort of authoritative balances in your system, though, unless you are planning to recompute all historical transactions any time you need a balance. You'll need to be able to do balance check-pointing or performance will quickly degrade at scale. Without check-pointed balances you'll also loose out on the benefits of balances carrying forward.

If you want authoritative balances, capable of making financial decisions, this pattern won't work at all. You'll need to update the balances of the accounts on either side of the transaction, as well as aggregate accounts that depend on it (see The Single Context Problem) which will make for very large DB transactions that are prone to failing. Small companies often want this level of consistency because they don't have multi-million dollar operational accounts and are very sensitive to loosing money.

Overall this one is a sometimes a benefit. Purely transactional systems that only move positive values through a workflow can often successfully build on top of this pattern, but you need to understand the trade offs. If you choose to build on transactions as a base unit you lose most of the benefits of stated above for both accounts and balances all. Expect significant work to be needed in order to ever display a balance, or do reporting if you choose this path.

The Devil is in the Details

This intro has been some high level summaries of the benefits of accounts/double-entry along with some very curtailed rebuttals, but its time to deep dive into the specific issues you'll find when working on accounts.

Next >