Accounting concepts for developers

11 minute read

Dave Stubbs

Accounting at its fundamental level is the process of recording and modelling financial information for a business. Accountants have spent hundreds of years perfecting abstractions and reports to delve into the inner workings of a business, for the owners, the management, regulators, and, of course, the people coming to collect your taxes.

This article is aimed at giving software developers working with financial data a brief overview of some of the key concepts involved, and how we might choose to model this. I'm not going to go into a lot of the detail — accountants have to deal with a lot of practical questions like, is a Jaffa Cake a cake? on which entire libraries have been written. I'll mark any rabbit holes like this with 🐇.

If you're looking for advice about how to run your business, find a friendly (and qualified) accountant to help you!

Double-entry bookkeeping

The aim of bookkeeping is to capture all transactions related to a business' finances. Accountants model this with a simple equation. Without using the proper jargon, this roughly says:

What you're worth = What you have - What you owe

This must always be true: the equation must always balance.

Double-entry bookkeeping keeps the equation balanced by requiring every transaction to have two balancing sides (a double-entry); if one side adds to "What you have", the other needs to add to "What you're worth" or remove from "What you owe", in order to keep the equation balanced.

We need to be a bit more precise than that, so we mark each side of the transaction against a specific thing. What the generic name for a thing is called can vary, but "Account" is common. These can be a real thing, like a bank account, or entirely virtual (or "nominal" 🐇), such as Sales or Food — these accounts all map onto the equation above, and we'll go into how in more detail later.

Let's show how this works with an example…

First step is we get paid… so add £100 to our bank account (yay!). How do we balance this? The money came from our employer's bank account, but that's not anything to do with our money and isn't relevant, so instead let's create a nominal account we'll just call "Salary", and make our double-entry transaction balance:

Bank     +£100
Salary   -£100

Why is Salary negative? This doesn't make a lot of sense conceptually, and is a bit confusing. So instead, amounts are split into either Debits (abbreviated Dr) or Credits (Cr). It's these Debits and Credits that double-entry accounting will always keep equal. How you use debits and credits depends on the context.

We'll call paying money into our bank account a debit (this is the convention). This then gives us:

         Debits  Credits
Bank     £100
Salary           £100
         £100    £100  ⬅︎ the totals should always be the same

When we pay off our credit card, the money is leaving the bank account, and going into the credit card account. We called paying money into the bank account a debit, so paying out must be the opposite: a credit. Paying off the credit card must be a debit in order to balance.

             Debits  Credits
Bank                 £5
Credit card  £5

Finally, we'll model the sandwich shop's account as another virtual account, and call it "Food". If paying off the credit card is a debit, spending on it must be a credit.

             Debits  Credits
Credit card          £5
Food         £5

At the end, we can sum up all the debits and credits across all of our accounts — the numbers should be equal.

             Debits  Credits
Bank         £100    £5
Credit card  £5      £5
Food         £5
Salary               £100
             £110    £110    ✔️

Some SQL

There are obviously a million ways to represent this, but at a basic level Countingup uses just two tables (which in reality are a little more complicated than this, because of many customers, and also 🐇)

The transactions table holds all the meta information about the transactions, like bank account details, payment references, and how you authorised the card payment, while the ledger_entries table does all of the accounting.

We can do some checks our double-entry accounting was done correctly:

SELECT SUM(credits) = SUM(debits) FROM ledger_entries
--> TRUE

SELECT SUM(credits) = SUM(debits) FROM ledger_entries
                                  WHERE transaction_id = 'tx_1';
--> TRUE

More on Accounts

How do we decide what nominal accounts to create? This is where the accountants shine — schemas for organising your accounts will depend on what type of business you have, what kind of regulations you need to adhere to, and what kind of tax you need to pay 🐇.

We can also split accounts into sub-accounts, and have a whole hierarchy to slice up your transactions at whatever resolution makes most sense. The important thing here is that we can only ever assign a particular credit or debit entry to a single account, and if we're summing up data we should be careful not to double-count.

 |- Countingup business account
 |- Savings
    |- Account 1
    |- Account 2
 |- Sales of product 1
 |- Other Sales
 |- Internet search
 |- App stores

To help interpret what the debits and credits mean, each account will be one of the following types 🐇 (names may vary). You can think of these as the ultimate parent accounts:

  • Assets — something that you have. The cash in your pocket, your bank account, a house, money others owe you. Debits increase assets.
  • Liability — an anti-asset, something that you owe. Loans, credit card balance, unpaid bills, unpaid tax. Credits increase liabilities.
  • Income — money coming in (revenue). Sales, interest payments on savings, your salary if you're an employee. Credits increase income.
  • Costs — money going out to run the business (expenses). Buying materials, equipment, paying staff, advertising. Debits increase costs.
  • Equity — investments in your business, or dividends you pay out 🐇. Credits increase equity.

There are lots of ways to try and remember which way round the debits and credits go, but I find it easiest just to start from a known transaction side (ie: "paying money into my bank account is a debit"), and then you can work out the rest by just balancing potential transactions (which is what we did in the example above).

There are a couple of equations from this that tell you useful things:

Your profit & loss:

Income - Costs = Profit (or Loss 😟) These accounts show you how much profit or loss you made over a given period, and reset at the beginning of each period 🐇.

When we reset them they're added to your Equity as a "Retained profit" — as such Income - Costs (your profit) is regarded as part of "what you're worth".

Balance sheet:

So we can now go back to our equation at the start:

What you're worth = What you have - What you owe Equity + Income - Costs = Assets - Liabilities

This is why in our first example, "Bank account" (asset, what you have) balances "Salary" (income, part of what you're worth).

A simplified version of this (with Equity rolling up all the profit) is known as the Accounting Equation:

Assets - Liabilities = Equity These accounts accumulate and show you how much they're worth at a particular point in time 🐇.

Implementing account hierarchies

Countingup applies a standard set of account codes for its customers depending on their business type, which are designed to allow normal tax filing. We have centralised meta-data on these codes:

func baseSA103Accounts() []*Account {
	return []*Account{
		{ID: "/sa103/sales", Name: "Sales", AccountType: AccountTypeIncome},
		{ID: "/sa103/officecosts", Name: "Admin", AccountType: AccountTypeExpense},
		{ID: "/sa103/equipment", Name: "Equipment", AccountType: AccountTypeAsset},
        // …

To keep the database simple the ledger entries just use the account code. We can then apply the relevant meta-data on aggregated data to build up reports as necessary within our service code (rather than in SQL).

For sub-accounts, the meta-data can include the parent-child relationship, and aggregate as needed.


We mentioned "point in time" and "period" just now. When things happen matters, e.g. which tax year it is, did I make a profit this month or last month, how much cash do I have right now?

In general accountants don't care about time 🐇, just the date, but when a transaction happens isn't as obvious as it might first appear. What transactions happen in the below example?

There's at least 2 ways to work this out, and there are plenty of regulations to say what you are and are not supposed to be doing.

  • Cash basis — transactions happen when money changes hands. The customer paid me on the 10th:

    10/4 Bank account  Dr £100
    10/4 Sales         Cr £100
  • Accruals basis — transactions happen when the goods/services are provided 🐇. So, let's say I provided the service when I finished building the shed (please talk to your accountant about this, there are so many rabbit holes here). We can invent an account to represent the money owed to us.

    Invoice: 5/4  Debtors       Dr £100
             5/4  Sales         Cr £100
    Payment: 10/4 Bank account  Dr £100
             10/4 Debtors       Cr £100 ⬅︎ this has now cancelled out the Debtors Dr

As such we can end up with a number of dates associated with any particular transaction, and sometimes we might need to reference different dates in different contexts 🐇:

  • Create, Update 🐇, Delete 🐇 dates and times which are mostly useful for audit purposes
  • Accrual date
  • Cash date (clearing date / settlement date)

Note: you can't move one side of a transaction to a different date from the other. If you did that the ledger wouldn't balance for those dates, which it must.

Some more SQL

Let's add dates to our database, then we can do some SQL queries for things we might actually want to know:

What's my Countingup business account's cleared balance right now?

SELECT SUM(debits) - SUM(credits) AS balance
    FROM transactions JOIN ledger_entries
    WHERE cleared_date <= NOW() AND account = 'Countingup';

What were all my liabilities at the start of the month?

SELECT SUM(credits) - SUM(debits) AS liabilities
    FROM transactions JOIN ledger_entries
    WHERE accrual_date <= '2022-04-01'
      AND account IN (/* liability_accounts */);

What is my profit (-ve loss) last month?

SELECT SUM(credits) - SUM(debits) AS profit
    FROM transactions JOIN ledger_entries
    WHERE accrual_date >= '2022-03-01' AND accrual_date <= '2022-03-31'
      AND account IN (/* income & costs accounts */);

Why can we just sum across income and costs here?

Profit = Income - Costs
       = (incomeCr - incomeDr) - (costsDr - costsCr)
       = incomeCr - incomeDr - costsDr + costsCr
       = (incomeCr + costsCr) - (incomeDr + costsDr)


You might notice some fun performance issues with the above: to get the current value of an asset I need to sum all transactions that have ever happened. That might not be so bad as a one-off, but as this is the first thing one of our customers sees when they log into the app, we probably need to optimise it a bit.

There's lots of ways to do this, and I don't want to go into too much detail here, but to summarise:

  • We can maintain a current balance whenever a transaction updates. We do this for the main current account balance as it's read so frequently.
  • We can store checkpoints at regular intervals. Calculate and store the accumulated debits and credits for each account code at the beginning of each month. You can then look up the previous cached values, and add to the sum of transactions that happened since to get the totals.

That's enough for now

There's plenty of detail, and complications of course (invoices, credit notes, journals, reports, VAT, to name but a few).

But hopefully you now have a basic understanding about how bookkeeping for accounting works.