The five account types

Assets, liabilities, equity, income, expenses: and why there are exactly five.

About 12 minutes

TheoryWhy this exists

Your ledger has forty accounts in it and every one of them has a balance. Cash RM24,300. Rent RM42,000. Sales RM310,000. Bank loan RM80,000. Trade receivables RM56,700.

Now answer two questions a business owner asks constantly: what is this business worth right now, and did it make money this year.

You cannot answer either one. Not because you are missing data — every figure you need is in that list. You cannot answer because a balance means nothing until you know what kind of thing the account describes. RM80,000 of cash and RM80,000 of bank loan are the same number and opposite facts.

Categories chosen by feel do not combine

The obvious fix is to label the accounts. Most people, left alone, invent categories that describe where the money lives or what it was for: "bank things", "customer things", "office costs", "one-off purchases".

That fails, and the reason is precise. A classification is only useful if the classes have arithmetic relationships to each other. You can total "office costs", but there is nothing you can add that total to, and nothing you can subtract it from, that yields a meaningful number. The categories are descriptive, not structural.

What you need is a classification whose class totals relate to each other by an identity. There is exactly one such classification, and you have already seen it.

Three types fall straight out of the equation

Assets = Liabilities + Equity

Every balance a business can hold at a point in time is one of three things: something the business controls, a claim on it by an outsider, or a claim on it by the owners.

  • Assets — what the business controls. Cash, receivables, equipment, inventory.
  • Liabilities — claims by outsiders. Supplier payables, loans, tax owed, wages accrued.
  • Equity — the residual claim of the owners. Capital contributed, plus profit retained.

There is no fourth possibility, and that is not a convention. An asset with no claim on it would be something the business controls that belongs to nobody. So these three classes are exhaustive by construction, and their totals must satisfy the identity. Classify every balance into one of the three and you can answer the first question: net worth is assets minus liabilities, which is equity.

Equity tells you that it moved, not why

Now try the second question. Equity was RM40,000 in January and RM58,000 in December. The business gained RM18,000 of owner value. Useful — and almost unusable.

Because RM18,000 could be the owner injecting RM5,000 of savings and trading producing RM13,000. Or trading losing RM7,000 while the owner injected RM25,000 to cover it. Those are opposite businesses with the same equity movement. And even once you know trading produced RM13,000, you still cannot see whether that came from RM310,000 of sales against RM297,000 of costs, or RM40,000 of sales against RM27,000 of costs. Two very different companies again.

The change in equity is a single number, and a single number cannot carry an explanation.

So you subdivide it. During the period, instead of writing every trading effect directly into equity, you record it in one of two temporary account types:

  • Income — events that increased equity through trading. Sales, fees, interest earned.
  • Expenses — events that decreased equity through trading. Rent, wages, utilities, cost of goods sold.

Income minus expenses is profit, and profit is the part of the equity movement that trading explains. Owner contributions and withdrawals stay in equity, where they belong, because they are not trading.

Which is why two of the five reset and three do not

This is the part most people are never told, and it makes the whole structure click.

Assets, liabilities and equity answer what is true right now. A bank balance of RM24,300 on 31 December is still RM24,300 on 1 January. Nothing resets, because the question is about a moment.

Income and expenses answer what happened during this period. Once the period ends and that question has been answered, the counters have done their job. Their accumulated effect is folded into equity as retained earnings, and they start again at zero for the next period. Sales of RM310,000 is a fact about a year, not a fact about a date.

That single difference is why there are two financial statements rather than one. The balance sheet reports the three permanent types at an instant. The profit and loss statement reports the two temporary types over a span. An account's type decides which statement it appears on — there is no separate decision to make.

Why exactly five

Try to invent a sixth. Every candidate collapses:

  • Owner drawings? A reduction of the owners' claim. Equity.
  • Accumulated depreciation? A negative asset. Still an asset, carried as a contra balance.
  • Cost of goods sold? A trading decrease. Expense.
  • A customer deposit for work not yet done? You owe them the work. Liability.

Anything you can record is either a thing controlled, a claim against it, or an explanation of how the owners' claim moved during the period. Five types is not a taxonomy someone chose. It is three from the equation plus two subdivisions of the third, and there is nowhere else for an account to go.

PracticeDoing it in Cynco

Open Accounting → Chart of accounts. What you are looking at is the complete list of buckets your business can post to, and every one of them carries a type.

Reading the codes

Accounts are numbered, and the numbers are not decoration. Banded ranges are a common charting convention, and where a chart uses them the bands are lined up with the five types:

BandTypeTypical accounts
1000–1999AssetsCash at bank, trade receivables, inventory, equipment
2000–2999LiabilitiesTrade payables, SST payable, bank loan, accrued wages
3000–3999EquityShare capital, retained earnings, owner drawings
4000–4999IncomeSales, service fees, other income
5000–5999ExpensesRent, wages, utilities, professional fees

The convention exists because a code sorts. List accounts by code and they come out grouped by type, in balance-sheet-then-P&L order, with no sorting logic required. It also means anyone reading a report can infer the type of account 5210 without being told.

The bands are for people, though. Nothing in the software reads them: the account's type field is what decides which statement a balance lands on and which direction increases it. An expense account numbered 1450 still behaves as an expense, and the report is still arithmetically right — but every human reading it will misread the code. So the band and the type should agree, always.

Charts vary. Some use 6000s onward to split expenses further — cost of sales in the 5000s, operating expenses in the 6000s. Others use a different scheme entirely, or no numbering at all. Read your own chart rather than assuming the ranges above.

What to look at

Scan the list and check three things:

  • Type on every row. Each account declares which of the five it is. Nothing is untyped.
  • Headers versus postable accounts. Some rows are group headings — "Current assets", "Operating expenses" — that exist to structure the report and hold no balance of their own. You cannot post to them. If an account looks like a category rather than a thing that happened, it is probably a header.
  • The gaps. Codes are usually spaced: 5100, 5200, 5300. The gaps are deliberate, so you can insert 5150 later without renumbering anything.

I am describing the general shape here rather than exact labels — screens move, and the naming varies by how your chart was set up. The structure underneath does not.

Try it yourself

Ten minutes, and it will change how you read every report afterwards:

  1. Open Accounting → Chart of accounts and note where each code band starts and stops in your chart.
  2. Pick five accounts at random. Before looking at the type column, decide for yourself which of the five types each one is. Then check.
  3. Find one account you were unsure about. Work out why: is it because the name is vague, or because you genuinely could argue for two types?

That third step is the useful one. An account whose type is arguable is an account people will post to inconsistently, and the reports built on it will drift. Rename it, or split it, before it collects a year of ambiguous entries.

The habit worth forming

When you create a new account, choose the type first and the name second. The name is for humans and can be changed. The type decides which statement the balance lands on, which direction increases it, and how the figure is interpreted for the life of the business.

TechnologyHow it is built

Account type looks like metadata. It is closer to a type annotation in a programming language: a small declaration that determines how every value in that account is interpreted downstream.

Why type is an enum and not a string

A varchar column called type accepts "Expense", "expense", "expenses", "Opex" and "". All five will be entered by someone eventually — a bulk import, an API client, a typo in a migration. And the failure is not a crash. It is a report that omits an account, because the query said where type = 'expense' and this row says Expense.

sql
account_type as enum ('asset', 'liability', 'equity', 'income', 'expense')

Five values, closed set, checked by the database. Anything else fails at write time, at the boundary, with a clear error — rather than at read time, silently, in a financial statement three months later.

The general principle: when a field has a fixed, small, slow-changing domain and downstream logic branches on it, make the domain part of the schema. Validation in application code is a good second layer, never the only one — the database is the only place every writer must pass through.

Two properties then come from the type rather than being stored alongside it:

ts
const increasesOnDebit = (t: AccountType) =>
  t === "asset" || t === "expense";

const statement = (t: AccountType) =>
  t === "income" || t === "expense" ? "profit_and_loss" : "balance_sheet";

Both are pure functions of the type. Storing them as separate columns would create the possibility of an account typed asset whose normal_balance says credit — a state the accounting model has no meaning for. Derive what can be derived; store only what is genuinely independent.

The bug a wrong type causes, and why balance checks miss it

Say you buy a batch of laptops for RM12,000 and post it to an account that should be an expense but is typed asset.

The entry balances. Debits equal credits. The trial balance ties. Every arithmetic check in the system passes, because the type does not participate in any of them.

What happens instead is that the account never appears on the profit and loss statement, so:

  • Profit for the period is overstated by RM12,000.
  • The balance sheet carries RM12,000 the business recognised as an asset when its policy required expensing. The laptops are real and the business has them — the error is the classification, not the existence.
  • Equity is overstated by RM12,000 — and it still equals assets minus liabilities, because both sides moved together.

The books are internally consistent and describe a business more profitable than the real one. This is the sharpest example of the point from module 1: the balance invariant guarantees that every event was recorded twice with matching amounts, and guarantees nothing about whether the accounts were the right accounts. Different classes of error need different defences. Arithmetic errors are caught by a constraint; classification errors are caught by review, and by making the type hard to get wrong in the first place.

A constraint can only protect the invariant it encodes. Balance checks encode arithmetic, not meaning.

Header accounts: a second flag with real teeth

A chart of accounts is a tree. "Operating expenses" has children; "Rent" does not. Only leaves should receive postings, which means the account row needs something like:

sql
is_postable boolean not null default true

and the posting path must reject a line naming a non-postable account.

Skip that check and you get a genuinely nasty bug class. Statements are usually built by walking the tree and summing leaf balances into their parents. A balance sitting on a parent has no leaf to be summed from, so it is invisible to the statement — while still being fully present in the trial balance. Your P&L and your trial balance disagree, both are computed correctly from the same data, and the discrepancy is a number nobody can locate by staring at either report.

Note where the check has to live: not in the account-creation screen, not in the journal entry form, but in the single write path that all ledger postings pass through. A rule enforced in the UI protects the users of that UI. A rule enforced at the chokepoint protects the ledger from every writer — imports, integrations, automated postings, and code that has not been written yet.