Skip to content

Writing

When one number has four owners

A clerk quoted a balance to somebody standing at the counter. The customer opened the portal on their phone and saw a different number. Both had come from the same system, and both were defensible.

The moment it became real

Somebody at a counter quoted a balance. The customer checked their phone. The two numbers differed by a meaningful amount.

That is a small incident and it has an outsized effect, because the customer does not conclude that one screen is wrong. They conclude that the system is unreliable. And once staff have been caught out twice, they stop trusting every number on every screen, including the ones that were always correct.

That is the actual cost of an inconsistency, and it is much larger than the inconsistency.

What was underneath

Four different parts of the system each computed what somebody owed.

Billing had one. The customer portal had another. The reporting layer had a third. Collections had a fourth.

None of them were sloppy. Each was written by somebody solving a real problem in front of them, each handled a slightly different set of edge cases, and each was individually defensible.

They diverged on three things: partial payments made in the middle of a period, credit carried forward from a previous period, and adjustments applied after an invoice had been raised.

Every one of those is a legitimate judgement call. The problem was that four judgement calls had been made independently, by different people, at different times, and nobody had ever written down which one was correct.

What I tried first

I made them agree.

Shared helpers for the tricky parts. A reconciliation pass over the edge cases. A test suite that ran all four implementations against the same fixtures and asserted equality.

It took about two weeks and at the end of it, all four agreed on every case I could construct. I was pleased with it.

It held for about a month.

Why it failed

The next feature needed the figure in a slightly different context, and the fastest correct path was to write a fifth calculation.

Not out of laziness, and not because anybody ignored my work. The codebase still contained four implementations of this calculation. In that context, adding a fifth is the consistent thing to do. It is what the architecture was telling everybody was normal.

That was the lesson, and I have thought about it more than any other single thing I have learned:

Agreement is not a fix. It is a truce, and truces expire.

Tests pin the behaviour that exists today. They do not prevent tomorrow’s implementation, because the design still permits one. I had spent two weeks treating a symptom with more care than anybody had previously applied, and the underlying disease was untouched.

There had been a signal on day two, which I noticed and did not act on. The shared helper needed a parameter so it could behave differently depending on which caller was using it. A helper that needs to know who is calling it is telling you the callers should not exist. I read that as a complication to work around rather than as the answer.

What actually worked

One component owns the calculation. Nothing else is permitted to compute it. Everything else asks.

The definition became explicit and singular, written down once in the language the business actually uses: what is outstanding for the current period, plus anything unpaid from earlier periods in the same financial year, plus anything carried forward from before that.

Every other subsystem became a reader.

The trade, honestly

This is not a free win and I want to state the cost properly, because “single source of truth” gets repeated as though it has no downside.

One component becomes a bottleneck. Everything touching money now queues behind changes to one file, and that file has more reasons to change than any other in the system.

A bug in it is a bug everywhere simultaneously. There is no partial blast radius any more.

And it is genuinely slower to build than letting each surface compute what it needs, which is exactly why the original four existed. Nobody set out to create four. Four is what you get by moving fast four times.

I took the trade on one argument: a single number that is wrong consistently is recoverable, and four numbers that disagree are not. With one owner, you fix it once and every surface is correct in the same deploy. With four, you are not fixing a bug, you are conducting an investigation into which of four answers was ever right.

The migration, which is the part I would repeat

Nothing switched over at once.

The new component was built alongside. Both figures were computed for every request. The difference was logged. Behaviour did not change at all.

That difference log turned out to be the most valuable artefact in the entire project. Every entry was either a bug in my new implementation or a behaviour of the old ones that nobody had documented.

Two of them were the second kind. Deliberate handling of specific situations, added years earlier for good reasons, that existed only in the code and in the memory of somebody who had since left. If I had switched over without comparing, I would have “fixed” both and broken something real for a small number of people who would have had a very difficult time explaining what had changed.

Only when every difference had an explanation did the reads move across, one surface at a time. The old calculations were deleted last, months later, when nothing referenced them.

What I carry forward

Ask whether the design permits the problem, not just whether the instance is fixed. If it permits it, it will recur, and your fix has a shelf life.

Run both and compare in production before switching anything. Synthetic tests only contain the cases you already thought of. The whole value is in finding the ones you did not.

When a shared helper needs to know its caller, stop. That is the design telling you something, and it took me two weeks to hear it.

Published June 2026, updated July 2026.

Hiring someone to own work like this?

I am open to senior backend and full stack roles, remote and permanent.

Get in touch / Read the CV