Skip to content
All writing
Architecture

Stop storing what you can derive

Invoice status, project progress, stock on hand. The bugs that make business software untrustworthy usually come from storing a value that should have been computed.

Published
9 May 2026
Reading time
5 minutes
Category
Architecture
Topics
Data modelling, CRM, Correctness

Here is a bug I have seen in almost every business system I have worked on: an invoice marked paid with a balance still outstanding. Nobody did anything wrong. A payment was deleted, or an item was edited, and the status column was not updated with it.

The root cause is duplication

The moment you store a status that is a function of other data, you have two sources of truth that can disagree. Every code path that touches a payment now has to remember to recompute the status. One that forgets creates a record that lies.

// Derived at read time — cannot drift, because there is nothing to keep in sync.
function displayStatus(invoice) {
  if (invoice.status === "cancelled") return "cancelled";
  if (invoice.amountPaid >= invoice.total) return "fully_paid";
  if (invoice.amountPaid > 0) return "partially_paid";
  if (isPast(invoice.dueDate)) return "overdue";
  return "not_paid";
}

What should still be stored

Facts and decisions. That a payment of ₹50,000 was received on the 3rd is a fact. That a user cancelled the invoice is a decision. Neither can be recomputed from anything else, so both must be stored.

Whether the invoice is overdue is neither — it is a conclusion from the due date and the payments. Store it and you have created a value that goes stale at midnight.

Store facts and decisions. Derive conclusions. If a field can be reconstructed from other fields, storing it is a synchronisation bug waiting for a deadline.

The same rule elsewhere

Project progress should come from completed tasks, not a percentage someone types. Stock on hand should come from the movement ledger, not a counter that drifts. In both cases the derived version is slightly more expensive to read and enormously cheaper to trust.

When to break it

Performance, and only with evidence. If a report genuinely cannot be computed fast enough, cache it — but treat the cache as a cache: give it a clear invalidation path and keep the derivation as the source of truth. That is very different from storing the value as if it were a fact.

The system behind this article

Proceed Digital — Agency CRM

Agency CRM tracking the full pipeline — lead, client, estimate, contract, project, task, invoice, payment — plus team, attendance and profit.

Read the case study