Ask most budgeting tools how to split a paycheck and they do the obvious thing: sort the budget lines by importance, pour income in until it runs out. The total comes out right and the detail comes out wrong.
It drains one deposit completely before touching the next, so it can never say “this paycheck covers most of the mortgage and a little of groceries” — which is exactly what happens in a real month. And because each line is solved on its own, there is nowhere to put a constraint you discover later: an account that needs a fixed amount, income that shouldn’t fund a particular category, a source that lands on the 15th.
Balance the grid instead
The better shape is a matrix: income sources down the side, budget lines across the top, and every cell a transfer. Two constraints have to hold at once — each row sums to what that source provides, each column sums to what that line needs.
That is a solved problem. Iterative proportional fitting alternately scales the rows to hit their totals and the columns to hit theirs, repeating until both hold. It is the same technique used to balance transportation matrices and contingency tables, and it converges in a few dozen rounds on a household-sized grid.
mortgage groceries fun = source
paycheck 1,732 892 376 3,000
rental 768 608 624 2,000
─────────────────────────────────────────────────
= budget 2,500 1,500 1,000 5,000
Every entry stays non-negative, and both constraints are satisfied together rather than in sequence. No line is starved because it happened to be last in the list.
Reliable money for the bills you can’t miss
Sources carry a stability rank and budget lines carry a priority. Dependable income gets steered toward the things you least want to bounce — salary toward the mortgage, freelance toward the travel fund.
These only seed the starting matrix. They shape which pairings are favoured; they never decide whether the plan balances. A strong preference cannot produce an unbalanced result, and there is a test asserting exactly that.
The totals almost never match
Both constraints can only hold when income equals budget, which in practice it does not. Rather than refusing to answer, the difference gets absorbed in a slack row or column:
- More income than budget — every line is funded in full and the remainder is reported as unassigned
- More budget than income — every dollar is spent and the lines are underfunded proportionally
Neither case distorts the real allocations, and neither is quietly swallowed. A shortfall is a fact about your month, not an error state.
It runs in your browser
No account, no backend. Household income and spending is not data that should sit on someone else’s server so a bit of arithmetic can happen, so the whole thing is client-side and persists to local storage.
That constraint is also why the engine is a pure function with no I/O: nothing to mock, nothing to stub, and 25 tests that assert properties rather than snapshots — rows sum to their source, columns sum to their target, nothing is ever negative, output is deterministic, and inputs are never mutated.
Where it came from
This started as a pile of Python scripts that rewrote a spreadsheet of my own budgets. Those could not be published: absolute paths into my Downloads folder, filenames naming my household, and someone else’s actual annual spending sitting in the source as a literal.
The algorithm was the part worth keeping. Extracting it meant separating the idea from the data it happened to be operating on — which is usually the difference between a script and a tool.
