A retirement projection can change even when you keep the starting balance, withdrawal amount, and return rate identical. I'll show how to audit the order of spending and investment growth, keep inflation adjustments consistent, and interpret the final row without mistaking a finite simulation for a guarantee.
I'm using a Vue calculator that produces a year-by-year table as the working example. The arithmetic is small enough to read in one sitting, but the labels need more care than the loop: a fixed-return scenario is not a forecast, and surviving a finite simulation is not the same as lasting forever.
Spend First, Then Grow What Remains
The inputs describe starting savings, a first-year annual withdrawal, an expected annual return, and inflation. Internally, percentage inputs become decimal rates. Savings and withdrawals must both be positive before the computed result returns a projection.
The central convention is that the withdrawal happens at the start of each year. Here is the loop from the implementation, with its inline comment omitted:
for (let y = 1; y <= MAX_YEARS; y++) { const withdrawThisYear = W * Math.pow(1 + inf, y - 1); let afterWithdraw = balance - withdrawThisYear; if (afterWithdraw <= 0) { rows.push({ year: y, withdraw: withdrawThisYear, balance: 0 }); yearsLasted = y - 1; depleted = true; break; } balance = afterWithdraw * (1 + r); rows.push({ year: y, withdraw: withdrawThisYear, balance }); yearsLasted = y; } With one million in savings, a 40,000 withdrawal, and a 5% return, the first year ends at 1,008,000: subtract 40,000, then multiply the remaining 960,000 by 1.05. Growing the original million first and subtracting afterward would produce 1,010,000.
That 2,000 difference is the return on money the person already spent. Neither ordering is universally wrong, but they describe different cash-flow assumptions. A monthly spending model would describe something else again. I prefer showing the convention next to the results instead of making readers infer it from a smooth chart.
The table stores each year's withdrawal and closing balance. That makes it possible to inspect the first row and check the arithmetic manually before trusting the longer projection. For financial UI work, a small auditable table can explain more than another decimal place.
Inflation Changes the Withdrawal, Not Just a Summary Label
The first withdrawal uses an exponent of zero, so it remains exactly the entered amount. In the following year, the withdrawal grows by one inflation factor; in year three, by two.
For example, with 2% inflation, a 40,000 initial withdrawal becomes 40,800 in year two and 41,616 in year three. Those are nominal amounts: the currency totals increase to represent the assumption that the same spending basket becomes more expensive.
The implementation also calculates a real-return summary:
const realReturn = (1 + r) / (1 + inf) - 1; At 5% return and 2% inflation, that is approximately 2.94%, rather than exactly 3%. Subtracting the rates is a useful approximation, but dividing the growth factors is the calculation actually used here.
An important detail: the loop still grows the balance using the nominal return r. The real return is explanatory output, not a second adjustment applied to the portfolio. Replacing r with the real return while continuing to increase withdrawals for inflation would mix accounting conventions and effectively penalize inflation twice.
You can model everything in today's purchasing power instead, but then the cash flows must use that convention consistently. I find this distinction easy to miss because both approaches can produce plausible-looking tables. The presence of an inflation input does not, by itself, tell you which currency basis a result uses.
The first-year withdrawal rate is similarly descriptive: it is W / S, not a rule that recalculates spending as a percentage of the remaining balance every year. Those two withdrawal policies behave very differently after losses.
A Depletion Row Has a Specific Meaning
When the next withdrawal would leave a balance of zero or less, the code adds a final row with a zero balance and stops. It sets yearsLasted to the number of earlier completed years.
That row records the planned withdrawal, not necessarily an amount the portfolio could fully pay. If the year starts with 10,000 but the planned withdrawal is 12,000, the table still contains 12,000 in its withdrawal column. It is not a cash ledger proving that 12,000 was available.
There is an equality boundary too: if savings exactly cover the next withdrawal, the <= 0 condition takes the depletion branch. Consequently, the completed-year count excludes that year, even though its start-of-year withdrawal could be paid exactly. Here, yearsLasted counts earlier years that reached the growth step; it does not count every fully funded annual withdrawal.
With savings of 100, annual withdrawals of 50, and zero return and inflation, year one closes at 50. The year-two withdrawal uses the remaining 50 exactly, but the result reports yearsLasted: 1. The English subtitle then substitutes that count into "your savings run out around year 1," although the zero-balance row is year two. That is a mismatch between the table and subtitle, not just rounding.
I would test this branch separately from the usual long-running example. Inputs that never enter the depletion path cannot tell you whether the last row, count, and explanatory text agree. Zero return and zero inflation make particularly useful fixtures because the expected arithmetic is transparent. A clearer UI would distinguish the depletion row's year from the completed-year count; the current implementation does not.
Sixty Years Is Not Indefinitely
The implementation caps the simulation at 60 years:
const MAX_YEARS = 60; After the loop, it sets sustainable to !depleted. That only establishes that the scenario did not hit the depletion condition during the simulated horizon. The current English headline says savings can last indefinitely; that wording is stronger than the calculation supports.
For a simple counterexample, take 1,000 in savings, a 10 annual withdrawal, and zero return and inflation. There is still money after year 60, but the balance plainly does not support withdrawals forever. This is a label limitation worth stating explicitly.
The model also omits taxes, fees, changing spending needs, and variable investment returns. A constant average return cannot demonstrate resilience to an unfortunate sequence of losses. I use this kind of calculation to understand assumptions and cash-flow mechanics, not to establish a safe withdrawal guarantee or give investment advice.
I turned that year-by-year calculation into a small free tool: Retirement Withdrawal Calculator.
