High-Yield Savings Accounts

TipHow these exercises work

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.

$10,000 at 4.35% APY: deposit and interest earned, year by year

$10,000 at 4.35% APY: deposit and interest earned, year by year

$10,000 at 4.35% APY: deposit and interest earned, year by year

$10,000 at 4.35% APY: deposit and interest earned, year by year

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)^C2

This 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.

$500 a month for 30 years at three APYs

$500 a month for 30 years at three APYs

$500 a month for 30 years at three APYs

$500 a month for 30 years at three APYs

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 - insured

Insured is the smaller of the balance and the limit.

balance <- 500000
insured <- min(balance, 250000)
balance - insured

4.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)