Do you really understand your accounts? Could you build your books from scratch?

If you run a business, you’ve probably heard of double-entry bookkeeping.

It’s “the most influential work in the history of capitalism”. We “may not have had the Industrial Revolution without it”. It’s kind of a big deal.

But, especially if you’re a technical founder, you might not actually understand it. You’re not alone: I conducted a survey of early-stage founders at our co-working space in Cambridge, with companies from 2 to 16 people in size. About 75% of founders said they didn’t understand bookkeeping! It seems that many founders are just mashing buttons in Quickbooks and hoping for the best.

But we’re hackers – we can do better than that! If you can write code, you can understand double-entry bookkeeping. And we’re going to prove it, by building an accounting app from scratch. All you need is a little bit of Python.

Part 1: The basics

There are two rules of double-entry bookkeeping:

  1. Every financial category in your business is represented by an account.

  2. Every financial transaction in your business can be represented as a transfer between accounts.

Let’s say my startup has $2,000 in the bank. I then spend $1,500 of that on a laptop. If I was just looking at the money in my bank account, I’d think that money just disappeared. But it didn’t disappear – it turned into a laptop!

We can record our new laptop in a separate account (we’ll call it “Fixed Assets”). Here’s how that looks:

Buying a laptop decreases Cash in Bank from $2,000 to $500, but increases Fixed Assets from $0 to $1,500

We can see that the total value of our company’s assets hasn’t changed: We’ve just traded $1,500 worth of cash for $1,500 worth of laptop.

The purchase is entered twice (hence “double entry”): it removes value from Cash in Bank, and adds it to Fixed Assets. The entries must always balance, so no value has disappeared – it’s just been moved around.

(We should note that we’re taking a very database-y view of our accounts here. An accountant wouldn’t talk about “moving value around”. Instead they would say, “we applied a $1,500 credit to Cash in Bank and a $1,500 debit to Fixed Assets” 1. We’re programmers, so I’m going to keep it simple and just talk about adding and subtracting numbers. From time to time, I’ll provide translations into accounting language, like this one. If you’re confused, start by ignoring everything in italics – once you understand how it all works, you can come back for the Accounting Jargon Decoder Ring.)

Let’s build it!

I think we understand enough to start building our accounting system. Let’s fire up Anvil, create a new app, and set up the built-in database (Data Tables) to represent our accounting history:

  • We make an Accounts table, with just a name column.
  • The Journal Entries table does the heavy lifting. It records the date and amount of every change made to every account.
  • To make it easier to check that every transaction is balanced, we group all the balancing entries for a transaction together: We make a table of Transactions with a description for each transaction, and then we link each journal entry to the transaction it’s part of.

Here’s our database, with two balancing journal entries representing a single transaction (buying a laptop):

Note: This guide includes screenshots of the Classic Editor. Since we created this guide, we've released the new Anvil Editor, which is more powerful and easier to use.

All the code in this guide will work, but the Anvil Editor will look a little different to the screenshots you see here!

Schema of Journal Entries table

Now we have a database, we can store transactions in it. Here’s the code to create a new transaction:

def add_new_transaction(date, from_account, to_account, amount, description):
	txn = app_tables.transactions.add_row(description=description, date=date)

		change=+amount, account=to_account, transaction=txn, date=date
		change=-amount, account=from_account, transaction=txn, date=date

For example, here some code we could run to record that laptop purchase:

  cash_in_bank = app_tables.accounts.get(name="Cash in Bank")
  fixed_assets = app_tables.accounts.get(name="Fixed Assets")

                      from_account=cash_in_bank, to_account=fixed_assets,
                      amount=1500, description="Purchased laptop")

Now we can make a web interface for entering transactions. We use Anvil’s visual designer to create a form for entering the transaction details. Then, when the user clicks Add Entry, we call our add_new_transaction() function:

Design your UI and then edit your code

To see the source code to our app so far, click here to open it in the Anvil editor:

Part 2: Dealing with the Real World

OK, so that’s the easy part – and this is where most introductions to double entry bookkeeping stop. But we still have a problem: How do we interact with the outside world?

As we keep saying, every transaction is balanced – it sums to zero. But a company is not a closed system! Sometimes, you spend money on something and it’s gone. If I spend $500 a month on delicious seasonal treats, that money really has left the company. Likewise, if I make something and sell it, I really have brought new money into the company.

But in the system we’ve designed so far, there’s no way to represent that. I could make an account representing “Things Meredydd Ate”, and record my Mini Egg habit by transferring $500 each month from Cash in Bank to Things Meredydd Ate. But as far as our database is concerned, the asset value of the company has not changed: I have just been steadily reallocating our assets from cash to chocolate.

This is clearly not an accurate representation of our business. What can we do to fix it?

“Balance Sheet” vs “Profit and Loss”

The answer is to separate our bookkeeping records: we create special accounts that represent “outside the company”, and treat them differently from things our company owns. We name them after the financial statements they’re used for:

  • Balance sheet accounts are the accounts we’ve already met. They represent things we own (assets, such as cash in a bank) or things we owe (liabilities, like a loan we will have to pay back – these have negative value).

    For any point in time, we can work out the value of each balance-sheet account on that date. This lets us display everything we owned or owed on that date: This is the company’s balance sheet.

  • Profit and loss accounts are accounts that represent the outside world. Transactions with these accounts represent income and expenses – that is to say, value flowing into or out of the company. For example, when we sell something, we can think of it as a transfer of money from “Sales” to a balance sheet account like “Cash in Bank”. When we spend money on Mini Eggs and I promptly eat them, that’s a transfer to “Expenses”.

    It doesn’t make sense to keep a running total of these (how much money does “the rest of the world” have, anyway?). We want to answer questions like, “how much did we spend on Mini Eggs web hosting this month?”

    For any period of time (eg “last year”), what we want to know is how much money the company has gained or lost from outside, and which P&L accounts it flowed from or to. We add up all transactions with every profit-and-loss account that occurred between the specified dates (eg from January 1st last year to January 1st this year), to get the profit and loss statement for that time period.

If you sum up all the accounts on a balance sheet on a given date, you know how much your company was worth (its “book value” on that date). If you calculate a profit and loss statement between two dates, you know how much profit or loss you made over that time period. Because all transactions balance, the total profit (or loss) between two dates is equal to the change in total book value between those two dates.

So we can see how much our company is worth right now (that’s the balance sheet for today), and how much it was worth last year (balance sheet for one year ago). And we can see how we got from there to here: where all those gains and losses came from (that’s the profit and loss statement from a year ago until today).

And because we’re using double-entry, this is an accurate way to measure profit! For example, buying new laptops for the team might deplete your bank account, but the company didn’t suddenly become unprofitable that month. The P&L lets you see through the noise in your bank account, and understand your actual financial position.

Implementing this in our app

Let’s add this distinction to our bookkeeping system. We add a new boolean column to the Accounts table, to record whether something is a balance-sheet or a profit-and-loss account. We’ll add a new account, “Sales”, which is not a balance-sheet account:

Adding a 'balance sheet' column to the Accounts table

We’ll put some sample data in to demonstrate. Let’s say we sold $2000 worth of widgets the week before we bought our laptop. We record that as a transfer from Sales (outside the company), to Cash in Bank:

Sample data indicating sales income before buying laptop

Notice that because we’ve gained value from the outside world, the change to the Sales account (profit and loss) is actually negative! This balances the positive change to Cash in Bank (balance sheet).

(Again, professional accountants would use different words here – they’d say “we applied a $2,000 credit to Sales, and a $2,000 debit to Cash in Bank” 2 – but the arithmetic is the same.)

Building a Balance Sheet

We can now add up each type of account separately, and calculate two important reports:

The Balance Sheet shows all our assets and liabilities at a given point in time. The total is the company’s “book value”:

A balance sheet

Here’s the code that calculates the balance of an account on a given date. We just add up all that account’s journal entries, up to the specified date:

def get_balance_for_account(account, date):
  balance = 0

  for entry in,
    balance += entry['change']

  return balance

Now we can calculate the balance for every balance-sheet account in our system:

def get_balance_sheet(date):
  return [{'name': account['name'], 'balance': get_balance_for_account(account, date)}
          for account in]

This produces a list of dictionaries, which is easy to display in a Data Grid. And that’s how we produce the balance sheet screen you see above.

Building a Profit and Loss Statement

Secondly, there’s the Profit and Loss Statement, which shows how much value the company gained from, or lost to, each P&L account over a given period:

A Profit and Loss statement

And here’s the code that calculates it for each account. It’s very similar to the balance-sheet code – after all, it’s just summing up all the changes to an account over a time period:

def get_profit_for_account(account, from_date, to_date):
  profit = 0

  for entry in,
                                                 date=q.between(from_date, to_date)):
    # Subtract, because any money going out of a P&L account
    # is a gain for us, and any money going in is a loss for us.
    profit -= entry['change']

  return profit

But there is one important difference. Profit and Loss accounts represent “outside the company”, and we want to count how much we’ve gained from P&L accounts – profit should be positive! Because journal entries always balance, we’ve already seen that a gain from Sales represents a negative change to the Sales account. So, to calculate our profit, we start at zero and subtract every change to the Profit and Loss account.

(An accountant would say, “when we display Profit and Loss accounts, we display credit balances as positive”. Again, the underlying arithmetic is the same: We add up everything in a Profit and Loss account, then flip the sign before we display it, so that negative [aka “credit”] numbers are displayed as positive.)

Source Code

That’s it! We’ve built a web-based accounting system with just a few lines of Python. We can enter transactions, and generate both balance sheets and profit-and-loss statements. In this article, we’ve been concentrating on the data-handling code – but thanks to Anvil, making a web-based user interface was the easy part.

Click here to open the app in the Anvil editor, read the source code, and try it out yourself:

Closing Thoughts

Should you use this code to manage your startup? Probably not. Use a full-featured accounting system like Quickbooks: it can import transactions from your bank, knows about tax regimes, handles multiple currencies, and much more. And you can still drive your accounting records with code – these systems have powerful APIs.

Of course, out-of-the-box tools can’t do everything. Your business will have processes you want to optimise, and common tasks you want to turn into one-click affairs. We build all our internal workflow tools with Anvil – we’ve written accounting tools that use the Quickbooks API to query transactions, record expenses and raise invoices. And now you understand how double-entry bookkeeping works, you’re ready to do the same thing!

What will you build?

Many thanks to our awesome accountants, Lucy and David Parry of Parry & Parry, who taught me everything in this post. Any mistakes in here are assuredly mine and not theirs. If you’re looking for accounting services in and around Cambridge or London, we recommend them heartily.

Epilogue: Examples

Still wondering how to represent a particular financial event? Here are a few examples to show how we use double-entry bookkeeping to represent what’s happening:

  • What happens when I issue an invoice?

    Businesses often sell things “on credit”: We give the customer a product, and issue them an invoice. Some time later, the customer pays the invoice.

    How to represent this? Well, if we’ve issued an invoice, the customer owes us money – so that’s an asset! We record these assets in their own balance-sheet account: “Accounts Receivable”.

    When we make a sale, we transfer value from the Sales account (that’s Profit and Loss, because we’re gaining value from the outside world) into Accounts Receivable. Later, when the customer pays the invoice, that’s a transfer from Accounts Receivable to Cash in Bank.

    Note how we made the profit when we made the sale, not when the money reached our bank account. But in between making the sale and receiving cold hard cash, we could track how much money we were owed by our customers. This gives us a lot more visibility into the business than just looking at our bank account.

  • What happens if someone doesn’t pay an invoice?

    If you’ve given up on ever getting money from someone, you write off the debt – it turns out this asset wasn’t worth as much as we had thought. This is a loss, so we record this as a transfer from Accounts Receivable to Sales. (The usual term for this is a “bad debt”.)

  • What happens when my laptop wears out?

    Again, we can represent this as a transfer from the balance-sheet to a P&L account. We typically model equipment as having a “useful economic life”, and spread its cost over that period. We call this “depreciating”. So if we depreciate our $1,500 laptop over 3 years, that means at the end of each year we transfer $500 from Fixed Assets (balance-sheet) to Depreciation (profit-and-loss). After three years, according to our accounting records, the laptop is now worthless.

    This neatly expresses that “we need to buy new laptops from time to time” is an ongoing issue. We don’t take a surprise loss every three years when we replace our laptops; we spread it out over the equipment’s useful life.


  1. Yes, accountants use “credit” for subtracting (negative numbers), and “debit” for adding (positive numbers). This might sound backwards to you. After all, when you deposit cash into your bank account, they “credit” you when they increase your balance, right? This is because your bank statement is written from the bank’s point of view. Every dollar in your bank account is a dollar the bank owes you. This means it’s a liability to them: it has negative value, so it’s a “credit”. To avoid this confusion, we’ll just talk about adding and subtracting numbers. ↩︎

  2. Yes, we used “credit” and “debit” correctly here too. Yes, it’s confusing. If you’re lost, just ignore the italicised sections and read the code – you’ll be fine. ↩︎