Inspect the Arithmetic — TFSA Over-Contribution Penalty Calculator
Version 1.0
Last updated: March 2026
Transparent arithmetic is the operating system of this calculator.
This document publishes the formulae, event-handling logic, and assumptions used to estimate CRA TFSA over-contribution penalties on the calculator page.
No opinions. No hidden assumptions. Just arithmetic.
Purpose
The calculator estimates the tax (penalty) that CRA may assess on TFSA over-contributions. It offers two modes: Simple (one excess amount over a fixed number of months) and Advanced (month-by-month with contributions, withdrawals, and room changes on specific dates). This page explains both methods.
A. Simple method
Simple mode is for a single lump-sum over-contribution that stayed at a constant amount for a known number of months.
Formula:
penalty = excessAmount × 1% × months
In plain language: you enter the excess amount (in dollars) and the number of whole months the excess remained. The estimate is 1% of the excess for each of those months. For example, $5,000 excess for 4 months gives penalty = 5,000 × 0.01 × 4 = $200.
When to use Simple: Only when the excess amount did not change over the period. If you had contributions or withdrawals that changed the excess during those months, use Advanced mode instead, which estimates the penalty based on the highest excess reached in each affected month.
B. Advanced method
Advanced mode estimates the penalty based on the highest excess amount in each calendar month. It processes your transactions in date order and applies the 1% rule to that highest excess per month.
1. Monthly penalty
For each calendar month in the calculation range:
monthly penalty = 0.01 × (highest excess amount in that month)
The rate is 1% (0.01). “Highest excess in that month” means the maximum value of excess reached at any point during the month, including after each transaction.
2. Total penalty
total penalty = sum of (monthly penalty) for every month in the date range
Months with no excess contribute zero to the total. The total is not “excess × number of months” unless excess is constant and unchanged for every month; the engine tracks the highest excess per month explicitly.
Why the highest excess in the month matters
CRA assesses the penalty each month based on the highest excess that existed during that month. So if you had $5,000 excess at the start of a month and then withdrew $3,000 on the 20th, your excess at the end of the month is $2,000 — but the month is still assessed on $5,000, because that was the highest excess reached in that month. A withdrawal later in the month does not erase an earlier higher excess for that same month.
The calculator therefore processes events in date order and, for each calendar month, records the maximum excess seen at any step during that month. That maximum is used for the monthly penalty.
Event-handling logic
Events are processed in chronological order. Two state variables are maintained:
- room — available TFSA contribution room (starts at your “starting room”, then adjusted by events)
- excess — amount by which contributions have exceeded room (the over-contribution)
Contribution
A contribution first uses available room; any amount that cannot be applied to room becomes (or adds to) excess.
useFromRoom = min(contribution amount, room)
room = room − useFromRoom
excess = excess + (contribution amount − useFromRoom)
Withdrawal
A withdrawal reduces current excess only. Withdrawals reduce excess from that point forward but do not restore same-year contribution room in this model. Room is only increased by room adjustments or by the annual Jan 1 room you specify.
excess = max(0, excess − withdrawal amount)
Room adjustment
A room adjustment (or the automatic Jan 1 new room) is applied once: it first absorbs current excess (reduces excess); only the remainder is added to available room. No double-counting.
absorbed = min(excess, amount)
excess = excess − absorbed
remaining = amount − absorbed
room = room + remaining
Timeline construction
- All transactions you enter with dates inside the calculation range are included; transactions outside the range are ignored.
- If you set annual new TFSA room (Jan 1) > 0, the engine inserts a room-adjustment event on January 1 for each year that falls within the range.
- Events are sorted by date and processed in order. For each calendar month, the engine records the highest excess reached during that month and applies the 1% formula.
Worked example
Using the following inputs:
- Start date: 2026-01-01
- End date: 2026-04-30
- Starting room: 0
- Annual Jan 1 room: 7,000
- Transactions:
- 2026-01-10 — contribution — 9,000
- 2026-02-15 — withdrawal — 1,000
- 2026-03-03 — contribution — 500
Timeline (sorted):
- 2026-01-01 — Jan 1 room: room = 0 + 7,000 = 7,000; excess = 0.
- 2026-01-10 — Contribution 9,000: useFromRoom = min(9,000, 7,000) = 7,000; room = 0; excess = 0 + 2,000 = 2,000.
January: Highest excess in month = 2,000. Monthly penalty = 0.01 × 2,000 = $20.
- 2026-02-15 — Withdrawal 1,000: excess = 2,000 − 1,000 = 1,000.
February: At start of month excess was 2,000; after withdrawal, 1,000. Highest in month = 2,000. Monthly penalty = 0.01 × 2,000 = $20.
- 2026-03-03 — Contribution 500: room = 0; useFromRoom = 0; excess = 1,000 + 500 = 1,500.
March: Highest excess = 1,500. Monthly penalty = 0.01 × 1,500 = $15.
April: No events. Excess remains 1,500. Monthly penalty = 0.01 × 1,500 = $15.
Totals:
total penalty = 20 + 20 + 15 + 15 = $70
months with excess = 4
peak excess = $2,000
ending excess = $1,500
Sample input/output contract
Input shape (engine):
{
startDate: "YYYY-MM-DD",
endDate: "YYYY-MM-DD",
startingRoom: number,
annualJan1Room: number,
transactions: [
{ date: "YYYY-MM-DD", type: "contribution" | "withdrawal" | "room_adjustment", amount: number },
...
]
}
Output shape (on success):
{
totalPenalty: number,
monthsWithPenalty: number,
peakExcess: number,
endingExcess: number,
endingRoom: number,
monthlyBreakdown: [ { month, highestExcess, monthlyPenalty, ... }, ... ],
normalizedTimeline: [ ... ],
assumptions: [ string, ... ]
}
On validation failure, the engine returns { error: "message" }.
Assumptions and limitations
- Penalty is 1% per month on the highest excess in each calendar month, consistent with CRA’s stated approach. Your actual assessment may depend on CRA’s records and filing details.
- Transactions outside the selected date range are ignored; the calculator does not load or assume events before the start date or after the end date.
- Withdrawals reduce excess only; they do not add to contribution room in this model (room is increased only by room adjustments or the annual Jan 1 value you enter).
- Jan 1 room is applied only when “annual new TFSA room (Jan 1)” is greater than zero.
- All amounts are in CAD. Dates are in YYYY-MM-DD. Money math uses floating point; rounding is applied for display only.
- This is an educational estimate. CRA rules and your specific situation may differ. This is not tax advice.
Regression tests
The engine includes a small regression harness. In the browser console (with the calculator page loaded) or in Node after loading the engine, run TfsaOverContributionEngine.runRegressionTests(). Test cases cover: no excess; single excess over multiple months; excess corrected mid-month; multiple contributions in one month; Jan 1 room eliminating excess; withdrawal after excess; room adjustment reducing excess.
If any discrepancy is identified between this documentation and the calculator output, the arithmetic in the engine governs.
Back to TFSA Over-Contribution Penalty Calculator