Automating Savings
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
- Why automation matters: willpower is finite; save first and spend what remains (Sethi’s Conscious Spending Plan)
- Money flow: split each paycheck on the first business day into savings, retirement, and checking
- Method 1, direct deposit split: the cleanest approach; savings leave the paycheck before it reaches checking
- Method 2, automatic bank transfers: the fallback; schedule a recurring transfer for the day after payday, never on payday itself
- Pay yourself first math: Monthly Savings = Gross Monthly Income × Savings Rate, often split across an emergency fund, 401(k) match, sinking funds, and investing
- Escalation: save half of every raise (Save More Tomorrow); stepping from 15% to 30% over seven years roughly doubles where you end up
- Troubleshooting: lower the amount if you keep pulling money back; automate a percentage of average income if pay varies
2 Pay Yourself First
Picture your monthly income as one bar. Your savings rate is the slice carved off before you see it; the rest is what you live on.
Formula: Monthly Savings Amount = Gross Monthly Income × Savings Rate
Example: $5,000 gross income at a 25% savings rate → $5,000 × 0.25 = $1,250 a month to automate.
How to read this result: this is the amount to schedule as an automatic transfer. If you keep moving money back to checking, the book suggests cutting it by 25 to 50% and trying again.
2.1 Level 1: Guided
Fill in the blanks to finish monthly_savings(), then find the amount to automate from $5,000 of gross monthly income at a 25% savings rate.
Put gross income in A2 (5000) and the rate in B2 (0.25). Find the monthly savings in C2.
=A2 * B2This returns $1,250.
Income times the rate.
def monthly_savings(gross_monthly_income, savings_rate):
return gross_monthly_income * savings_rate
monthly_savings(5000, 0.25)Income times the rate.
monthly_savings <- function(gross_monthly_income, savings_rate) {
gross_monthly_income * savings_rate
}
monthly_savings(5000, 0.25)2.2 Level 2: Practice
The book splits the 25% four ways: emergency fund 10%, 401(k) match 5%, sinking funds 5%, and investing 5%. How much goes to the emergency fund each month?
Put the destinations in A2:A5 and percentages in B2:B5. Find each amount in C.
In C2, filled down: =5000 * B2. The emergency fund gets $500, and =SUM(C2:C5) checks back to $1,250.
monthly_savings() works on the whole pct column at once.
split["amount"] = monthly_savings(5000, split["pct"])
split.loc[split["destination"] == "Emergency fund", "amount"].iloc[0]monthly_savings() works on the whole pct column at once.
split |>
mutate(amount = monthly_savings(5000, pct)) |>
filter(destination == "Emergency fund") |>
pull(amount)2.3 Level 3: Challenge
Freelance pay varies: the last six months were $4,200, $5,600, $3,900, $6,100, $4,800, and $5,400. To make sure the automatic transfer never bounces, base it on your lowest month. How much do you automate at 25%?
Put the six months in A2:A7. Find the safe transfer in one formula.
=MIN(A2:A7) * 25%This returns $975. The six-month average ($5,000) would give $1,250, which a $3,900 month can’t cover comfortably.
min(last_6) finds the lowest month.
last_6 = [4200, 5600, 3900, 6100, 4800, 5400]
monthly_savings(min(last_6), 0.25)min(last_6) finds the lowest month.
last_6 <- c(4200, 5600, 3900, 6100, 4800, 5400)
monthly_savings(min(last_6), 0.25)3 Direct Deposit Split
The book’s Method 1 example: a $5,000 gross paycheck, a 25% savings goal, a 10% 401(k) contribution taken before taxes, and about $750 in taxes. The 401(k) already covers part of the goal, so the HYSA only needs the rest.
Formula: HYSA Split = (Gross × Savings Rate) − 401(k); Checking Split = Gross − 401(k) − Taxes − HYSA Split
Example: 401(k) = $500, so the HYSA split is $1,250 − $500 = $750. The deposit is $5,000 − $500 − $750 = $3,750, and checking gets $3,750 − $750 = $3,000.
How to read this result: the HYSA and checking numbers are what you type into your payroll portal. The 401(k) and taxes are handled by payroll before the deposit.
3.1 Level 1: Guided
Fill in the blanks so deposit_split() sends the rest of the 25% goal to the HYSA after the 401(k), then find the HYSA split for a $5,000 paycheck with a 10% 401(k) and $750 of taxes.
Put gross pay in A2 (5000), the savings rate in B2 (0.25), the 401(k) rate in C2 (0.10), and taxes in D2 (750). Find the 401(k) in E2 and the HYSA split in F2.
=A2 * C2
=A2 * B2 - E2The 401(k) takes $500 and the HYSA gets $750.
The full savings goal is gross × savings rate. The 401(k) already covers part of it.
def deposit_split(gross, savings_rate, k401_rate, taxes):
k401 = gross * k401_rate
hysa = gross * savings_rate - k401
deposit = gross - k401 - taxes
checking = deposit - hysa
return {"k401": k401, "taxes": taxes, "deposit": deposit, "hysa": hysa, "checking": checking}
deposit_split(5000, 0.25, 0.10, 750)["hysa"]The full savings goal is gross × savings rate. The 401(k) already covers part of it.
deposit_split <- function(gross, savings_rate, k401_rate, taxes) {
k401 <- gross * k401_rate
hysa <- gross * savings_rate - k401
deposit <- gross - k401 - taxes
checking <- deposit - hysa
data.frame(k401, taxes, deposit, hysa, checking)
}
deposit_split(5000, 0.25, 0.10, 750)$hysa3.2 Level 2: Practice
Using the same paycheck, how much lands in checking for spending?
Find the deposit in G2 and checking in H2.
=A2 - E2 - D2
=G2 - F2The deposit is $3,750 and checking gets $3,000.
The function returns a dict; ask for the "checking" key.
deposit_split(5000, 0.25, 0.10, 750)["checking"]The function returns a data frame; pull out the checking column.
deposit_split(5000, 0.25, 0.10, 750)$checking3.3 Level 3: Challenge
If the 401(k) rate is 30%, it alone beats the 25% goal and the HYSA split goes negative. Change deposit_split() so the HYSA split never drops below zero, then find the HYSA split for 401(k) rates of 5%, 10%, 15%, and 30%.
Put the four 401(k) rates in C2:C5 and fix the HYSA formula so it can’t go negative.
=MAX(A2 * B2 - A2 * C2, 0)The HYSA splits are $1,000, $750, $500, and $0.
Rewrite deposit_split() with hysa = max(gross * savings_rate - k401, 0), then use a list comprehension over rates.
def deposit_split(gross, savings_rate, k401_rate, taxes):
k401 = gross * k401_rate
hysa = max(gross * savings_rate - k401, 0)
deposit = gross - k401 - taxes
checking = deposit - hysa
return {"k401": k401, "taxes": taxes, "deposit": deposit, "hysa": hysa, "checking": checking}
rates = [0.05, 0.10, 0.15, 0.30]
[deposit_split(5000, 0.25, k, 750)["hysa"] for k in rates]Rewrite deposit_split() with hysa <- pmax(gross * savings_rate - k401, 0). pmax() works on a whole vector of rates, so pass rates directly.
deposit_split <- function(gross, savings_rate, k401_rate, taxes) {
k401 <- gross * k401_rate
hysa <- pmax(gross * savings_rate - k401, 0)
deposit <- gross - k401 - taxes
checking <- deposit - hysa
data.frame(k401, taxes, deposit, hysa, checking)
}
rates <- c(0.05, 0.10, 0.15, 0.30)
deposit_split(5000, 0.25, rates, 750)$hysa4 Escalating with Raises
Save half of every raise. Your spending still grows a little, and your savings rate climbs without a budget cut.
Formula: Extra Monthly Savings = (New Income − Old Income) × 0.5 ÷ 12; 30 Year Value = Monthly × ((1 + r/12)^360 − 1) ÷ (r/12)
Example: a raise from $50,000 to $52,000 adds $1,000 a year ($83.33 a month) to savings. In the book’s escalation table, $625 a month at 7% for 30 years grows to about $762k, and $1,575 a month grows to about $1.92M.
How to read this result: each step up in the savings rate adds hundreds of thousands of dollars after 30 years. The raises pay for it, so the money you live on never shrinks.
4.1 Level 1: Guided
Fill in the blanks to finish raise_to_savings(), then find the extra monthly savings from a raise from $50,000 to $52,000 when you save half of it.
Put the old income in A2 (50000) and the new income in B2 (52000). Find the extra monthly savings in C2.
=(B2 - A2) * 0.5 / 12This returns $83.33.
The raise is the new income minus the old income.
def raise_to_savings(old_income, new_income, share=0.5):
return (new_income - old_income) * share / 12
raise_to_savings(50000, 52000)The raise is the new income minus the old income.
raise_to_savings <- function(old_income, new_income, share = 0.5) {
(new_income - old_income) * share / 12
}
raise_to_savings(50000, 52000)4.2 Level 2: Practice
Year 1 of the book’s escalation table saves 15% of $50,000, which is $625 a month. What does $625 a month grow to in 30 years at 7%?
Find the 30-year value in one formula.
=FV(7%/12, 360, -625)This returns $762,481.87.
The rate and years default to 7% and 30, so pass the monthly amount.
fv_monthly(625)The rate and years default to 7% and 30, so pass the monthly amount.
fv_monthly(625)4.3 Level 3: Challenge
By year 7 of the escalation table, income is $63,000 and the savings rate is 30%. How much more does year 7’s monthly amount grow to in 30 years than year 1’s $625?
Put year 1 (50000, 0.15) and year 7 (63000, 0.30) in rows 2 and 3. Find each monthly amount and 30-year value, then the difference.
Monthly in C2, filled down: =A2*B2/12. Value in D2, filled down: =FV(7%/12, 360, -C2). Then =D3 - D2 returns $1,158,972.45 ($1,921,454 vs. $762,482).
esc["monthly"] = esc["income"] * esc["rate"] / 12, then fv_monthly() on that column. Pick rows with esc.loc[esc["year"] == 7, "value"].iloc[0].
esc["monthly"] = esc["income"] * esc["rate"] / 12
esc["value"] = fv_monthly(esc["monthly"])
esc.loc[esc["year"] == 7, "value"].iloc[0] - esc.loc[esc["year"] == 1, "value"].iloc[0]mutate(monthly = income * rate / 12, value = fv_monthly(monthly)), then summarise(gap = value[year == 7] - value[year == 1]).
esc |>
mutate(
monthly = income * rate / 12,
value = fv_monthly(monthly)
) |>
summarise(gap = value[year == 7] - value[year == 1]) |>
pull(gap)




