High-Yield Savings Accounts
Each topic starts with a real scenario and a bar model, then three levels of exercises. Level 1 (Guided) fills in the blanks of a finished function. Level 2 (Practice) applies it to new numbers. Level 3 (Challenge) asks a new question with little scaffolding. The R and Python exercises run in your browser: click Run Code to try, Submit Answer to check, and use the hint or solution if you get stuck. Excel exercises have a solution you can reveal.
1 Book Topics
- Separate account: the real benefit of a savings account at a different bank is friction; 1 to 2 days to move money back stops impulse spending
- Choosing an account: need it within 6 months? Use a HYSA. Can you lock it away for 3 to 60 months? Consider a CD
- Comparing HYSAs: online banks pay about 4 to 5% APY and differ by only 0.1 to 0.4%; all of them beat the 0.01% at traditional banks by about 400×
- APY vs. APR: APY already includes daily compounding, so balance growth is Starting Balance × (1 + APY)^years
- FDIC insurance: covers up to $250,000 per account holder per bank; money market accounts are a close alternative with possible withdrawal limits
- Savings hierarchy: emergency fund, sinking funds, and short term goals go in a HYSA; 3 to 5 year goals in CDs or short term bonds; 5+ years goes to investing
2 HYSA Balance
APY is the total return for a year with compounding already included. To grow a balance, multiply by (1 + APY) once for each year you hold the money.
Formula: Ending Balance = Starting Balance × (1 + APY)^years
(The daily math belongs to the APR: APY = (1 + APR ÷ 365)^365 − 1.)
Example: $10,000 at 4.35% APY grows to $10,000 × 1.0435 = $10,435 after one year ($435 in interest) and to $12,373 after five years.
How to read this result: the green layer is money the bank paid you. On $10,000 the gap between online banks is about $20 a year, but the gap between any of them and a traditional bank is over $400.
2.1 Level 1: Guided
Fill in the blanks to finish hysa_balance(), then find what $10,000 grows to after one year at 4.35% APY.
Put the deposit in A2 (10000), the APY in B2 (0.0435), and years in C2 (1). Find the balance in D2.
=A2 * (1 + B2)^C2This returns $10,435.
The APY already includes compounding, so grow by (1 + annual_apy) once per year.
def hysa_balance(principal, annual_apy, years=1):
return principal * (1 + annual_apy) ** years
hysa_balance(10000, 0.0435)The APY already includes compounding, so grow by (1 + annual_apy) once per year.
hysa_balance <- function(principal, annual_apy, years = 1) {
principal * (1 + annual_apy)^years
}
hysa_balance(10000, 0.0435)2.2 Level 2: Practice
Leave the same $10,000 alone for 5 years. What is the balance?
Change C2 to 5.
The same formula returns $12,372.64.
Pass the deposit, the APY, and years=5.
hysa_balance(10000, 0.0435, years=5)Pass the deposit, the APY, and years = 5.
hysa_balance(10000, 0.0435, years = 5)2.3 Level 3: Challenge
Compare one year on $10,000 at four banks: Regular bank 0.01%, Ally 4.20%, Discover 4.35%, and Amex 4.40%. How much more interest does the best bank pay than the worst?
Put the banks in A2:A5 and APYs in B2:B5. Find each year’s interest in C, then the gap.
In C2, filled down: =10000*B2. Then:
=MAX(C2:C5) - MIN(C2:C5)This returns $439 ($440 vs. $1).
banks["interest"] = hysa_balance(10000, banks["apy"]) - 10000, then .max() - .min() on that column.
banks["interest"] = hysa_balance(10000, banks["apy"]) - 10000
banks["interest"].max() - banks["interest"].min()mutate(interest = hysa_balance(10000, apy) - 10000), then summarise(gap = max(interest) - min(interest)) and pull(gap).
banks |>
mutate(interest = hysa_balance(10000, apy) - 10000) |>
summarise(gap = max(interest) - min(interest)) |>
pull(gap)3 Small Rate Gaps Over Time
The book says a 0.3% difference looks like noise at first but adds up over 30 years of regular deposits. To check, we need the growth of a monthly deposit, using the monthly rate that matches the APY.
Formula: Future Value = Deposit × ((1 + i)^(12 × years) − 1) ÷ i, where i = (1 + APY)^(1/12) − 1
Example: $500 a month for 30 years grows to about $342,635 at 4.0% APY and $360,801 at 4.3% APY, a gap of about $18,166.
How to read this result: the gap is small for the first few years and grows with time. It is worth picking a good rate once, but not worth switching banks every month to chase 0.05%.
3.1 Level 1: Guided
Fill in the blanks to finish hysa_deposits_fv(), then find what $500 a month grows to in 30 years at 4.0% APY.
Put the deposit in A2 (500), the APY in B2 (0.04), and years in C2 (30). Find the balance in D2.
=FV((1+B2)^(1/12)-1, 12*C2, -A2)This returns $342,635.28.
Turn the APY into a monthly rate with the 12th root, then compound once per month.
def hysa_deposits_fv(monthly_deposit, annual_apy, years):
i = (1 + annual_apy) ** (1 / 12) - 1
return monthly_deposit * ((1 + i) ** (12 * years) - 1) / i
hysa_deposits_fv(500, 0.04, 30)Turn the APY into a monthly rate with the 12th root, then compound once per month.
hysa_deposits_fv <- function(monthly_deposit, annual_apy, years) {
i <- (1 + annual_apy)^(1 / 12) - 1
monthly_deposit * ((1 + i)^(12 * years) - 1) / i
}
hysa_deposits_fv(500, 0.04, 30)3.2 Level 2: Practice
Another bank pays 4.3% instead of 4.0%. Over the same 30 years of $500 deposits, how much bigger is the balance?
Add the 4.3% row and find the gap.
=FV(1.043^(1/12)-1, 360, -500) - FV(1.04^(1/12)-1, 360, -500)This returns $18,165.86.
Same deposit and years, APY 0.043.
hysa_deposits_fv(500, 0.043, 30) - hysa_deposits_fv(500, 0.04, 30)Same deposit and years, APY 0.043.
hysa_deposits_fv(500, 0.043, 30) - hysa_deposits_fv(500, 0.04, 30)3.3 Level 3: Challenge
Small gaps take time to matter. Of the horizons 5, 10, 20, and 30 years, which is the first where the 4.3% account is ahead by more than $5,000?
Put the horizons in A2:A5, the gap for each in B2:B5, and find the first above $5,000.
In B2, filled down: =FV(1.043^(1/12)-1, 12*A2, -500) - FV(1.04^(1/12)-1, 12*A2, -500). Then:
=INDEX(A2:A5, MATCH(TRUE, B2:B5>5000, 0))The answer is 20 years ($6,018). At 10 years the gap is only $1,128.
Compute the gap with both APYs for each horizon, then use next(y for y in horizons if ...) or a loop with break.
horizons = [5, 10, 20, 30]
next(y for y in horizons if hysa_deposits_fv(500, 0.043, y) - hysa_deposits_fv(500, 0.04, y) > 5000)The function is vectorized: compute both balances for all horizons, subtract, and use min(horizons[...]).
horizons <- c(5, 10, 20, 30)
gap <- hysa_deposits_fv(500, 0.043, horizons) - hysa_deposits_fv(500, 0.04, horizons)
min(horizons[gap > 5000])4 FDIC Coverage
FDIC insurance covers up to $250,000 per account holder per bank. Anything above that at one bank is not insured.
Formula: Insured = smaller of (Balance, $250,000); Uninsured = Balance − Insured; Banks Needed = Balance ÷ $250,000, rounded up
Example: a $10,000 balance is fully insured. A $500,000 balance at one bank has $250,000 uninsured, so it needs two banks.
How to read this result: if the uninsured column is above zero, move that amount to an account at a different bank.
4.1 Level 1: Guided
FDIC insurance covers up to $250,000 per depositor per bank. Fill in the blanks to find the uninsured part of $500,000 at one bank.
Put the balance in A2 (500000). Find the insured amount in B2 and the uninsured amount in C2.
=MIN(A2, 250000)
=A2 - B2$250,000 is uninsured.
Insured is the smaller of the balance and the limit.
balance = 500000
insured = min(balance, 250000)
balance - insuredInsured is the smaller of the balance and the limit.
balance <- 500000
insured <- min(balance, 250000)
balance - insured4.2 Level 2: Practice
How many banks do you need to fully insure $610,000?
Put the balance in A2 (610000). Find the banks needed.
=ROUNDUP(A2 / 250000, 0)This returns 3.
Divide by the limit and round up; 2.44 banks means you need 3.
import math
math.ceil(610000 / 250000)Divide by the limit and round up; 2.44 banks means you need 3.
ceiling(610000 / 250000)4.3 Level 3: Challenge
Four savers each keep everything at one bank: $10,000, $250,000, $500,000, and $610,000. How much money is uninsured in total?
Put the balances in A2:A5, the uninsured amount in B2:B5, and total it.
In B2, filled down: =A2 - MIN(A2, 250000). Then =SUM(B2:B5) returns $610,000.
savers["balance"].clip(upper=250000) caps every balance at the limit.
savers["insured"] = savers["balance"].clip(upper=250000)
savers["uninsured"] = savers["balance"] - savers["insured"]
savers["uninsured"].sum()min() returns one number for the whole column. pmin(balance, 250000) works row by row.
savers |>
mutate(
insured = pmin(balance, 250000),
uninsured = balance - insured
) |>
summarise(total = sum(uninsured)) |>
pull(total)


