Index Funds

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

  • What an index fund is: an index is a list of companies (like the S&P 500); an index fund buys everything on the list, weighted by company size (market cap), with no manager picking winners (passive vs. active)
  • Why index funds win: SPIVA shows 79% of large US active funds trailed the S&P 500 in 2025 and about 9 in 10 over 15 years; Bogle’s math says the average investor earns the market return minus costs
  • Expense ratios: a yearly fee taken out quietly; 0.04% vs. 1% is $4 vs. $100 on $10,000; look for funds under 0.10%
  • Total market vs. S&P 500 vs. international: the first two move almost together; experts disagree on how much international to hold (Bogle and Collins little or none, Felix anywhere from 10% to 60% home country)
  • ETFs vs. mutual funds: same holdings, different packaging; ETFs trade all day and are a little more tax efficient, mutual funds make automatic investing easier
  • How to pick one: go broad, check the expense ratio, look for “index” in the 401(k) list (or a target date fund), automate, and leave it alone

2 The Fee This Year

Every year, the fund takes a slice of your balance as its fee.

Formula: Yearly fee = Balance × Expense ratio

Example: $10,000 × 0.0004 = $4. $10,000 × 0.01 = $100, or 25 times as much.

One year of fees on $10,000

One year of fees on $10,000

One year of fees on $10,000

One year of fees on $10,000

The second example uses a table of typical expense ratios across a few balances. The fee grows with your balance, so a fee that costs pocket change at $10,000 costs real money at $250,000.

Read the fee as dollars you never see. The active fund costs $2,500 a year on $250,000, and the index fund costs $100.

2.1 Level 1: Guided

Fill in the blanks to finish yearly_fee(), then find this year’s fee on $10,000 in an index fund with a 0.04% expense ratio.

Put the balance in A2 (10000) and the expense ratio in B2 (0.0004). Find the fee in C2.

=A2 * B2

This returns $4.

The fee is a percentage of your balance. 0.04% is 0.0004.

def yearly_fee(balance, expense_ratio):
    return balance * expense_ratio

yearly_fee(10000, 0.0004)

The fee is a percentage of your balance. 0.04% is 0.0004.

yearly_fee <- function(balance, expense_ratio) {
  balance * expense_ratio
}
yearly_fee(10000, 0.0004)

2.2 Level 2: Practice

An active fund charges 1%. What is this year’s fee on a $250,000 balance?

Change A2 to 250000 and B2 to 0.01.

The same formula returns $2,500, every year, whether the fund does well or not.

Write 1% as 0.01.

yearly_fee(250000, 0.01)

Write 1% as 0.01.

yearly_fee(250000, 0.01)

2.3 Level 3: Challenge

Your portfolio holds $40,000 in an index fund (0.04%), $25,000 in a target-date fund (0.10%), and $10,000 in an active fund (1%). What is the blended expense ratio of the whole portfolio? Give a decimal.

Put balances in B2:B4 and ratios in C2:C4. Find the total fee and divide by the total balance.

=SUMPRODUCT(B2:B4, C2:C4) / SUM(B2:B4)

This returns 0.00188, or 0.188%. The $10,000 active fund causes most of it ($100 of the $141 in fees).

Add a fee column with yearly_fee(), then portfolio["fee"].sum() / portfolio["balance"].sum().

portfolio["fee"] = yearly_fee(portfolio["balance"], portfolio["expense_ratio"])
portfolio["fee"].sum() / portfolio["balance"].sum()

mutate(fee = yearly_fee(balance, expense_ratio)), then summarise(blended = sum(fee) / sum(balance)).

portfolio |>
  mutate(fee = yearly_fee(balance, expense_ratio)) |>
  summarise(blended = sum(fee) / sum(balance)) |>
  pull(blended)

3 What Fees Cost Over a Lifetime

The fee comes out of the return, so subtract it from the return before you compound.

Formula: FV = PMT × [((1 + r)^n − 1) ÷ r], where r = (return − expense ratio) ÷ 12

Example: $300/month for 40 years at 7%. The index fund (0.04%) grows to about $778,655 and the active fund (1%) to about $597,447. Both people put in the same $144,000. The 1% fee cost $181,207, about 23% of what the index fund investor ended up with.

$300/month at 7%, with a 0.04% fee vs. a 1% fee

$300/month at 7%, with a 0.04% fee vs. a 1% fee

$300/month at 7%, with a 0.04% fee vs. a 1% fee

$300/month at 7%, with a 0.04% fee vs. a 1% fee

The second example tries several expense ratios over 40 years and reports the share of the low cost result each one loses. The edge case is a 0% fee, which is just the plain annuity from Investing Basics.

Read the lifetime cost as a share, not a dollar amount. A 1% fee takes about 23% of the ending balance, which is why the book says close to a quarter. A negative “lost” number means that fund beat the 0.04% index fund (only possible here with a lower fee).

3.1 Level 1: Guided

Fill in the blanks to finish future_value_after_fees(), then find what $300 a month grows to over 40 years at 7% in an index fund charging 0.04%.

Find the value in one formula with the fee taken off the return.

=FV((7%-0.04%)/12, 480, -300)

This returns $778,654.71.

The fee comes straight off the return. 0.04% is 0.0004.

def future_value_after_fees(contribution, annual_return, expense_ratio, years):
    rate = (annual_return - expense_ratio) / 12
    periods = years * 12
    return contribution * (((1 + rate) ** periods - 1) / rate)

future_value_after_fees(300, 0.07, 0.0004, 40)

The fee comes straight off the return. 0.04% is 0.0004.

future_value_after_fees <- function(contribution, annual_return, expense_ratio, years) {
  rate    <- (annual_return - expense_ratio) / 12
  periods <- years * 12
  contribution * (((1 + rate)^periods - 1) / rate)
}
future_value_after_fees(300, 0.07, 0.0004, 40)

3.2 Level 2: Practice

Same $300 a month for 40 years at 7%, but in an active fund charging 1%. How much does the fee cost compared with the index fund?

Find both values and the difference.

=FV((7%-0.04%)/12, 480, -300) - FV((7%-1%)/12, 480, -300)

This returns $181,207.49.

Only the expense ratio changes: 0.01.

index_fund = future_value_after_fees(300, 0.07, 0.0004, 40)
active_fund = future_value_after_fees(300, 0.07, 0.01, 40)
index_fund - active_fund

Only the expense ratio changes: 0.01.

index_fund <- future_value_after_fees(300, 0.07, 0.0004, 40)
active_fund <- future_value_after_fees(300, 0.07, 0.01, 40)
index_fund - active_fund

3.3 Level 3: Challenge

Compare expense ratios of 0%, 0.04%, 0.10%, 0.50%, 1%, and 1.5% over the same 40 years. Which is the lowest ratio that loses more than 10% of the index fund’s (0.04%) ending balance?

Put the ratios in A2:A7, the ending balance in B, and the share lost in C. Find the first ratio above 10%.

In B2: =FV((7%-A2)/12, 480, -300). In C2: =1 - B2/$B$3. Fill down, then:

=INDEX(A2:A7, MATCH(TRUE, C2:C7>0.1, 0))

The answer is 0.50%, which loses 12% of the ending balance.

Share lost is 1 - fv / index_fund. Use min(er for er in ratios if ...).

index_fund = future_value_after_fees(300, 0.07, 0.0004, 40)
ratios = [0, 0.0004, 0.001, 0.005, 0.01, 0.015]
min(er for er in ratios if 1 - future_value_after_fees(300, 0.07, er, 40) / index_fund > 0.10)

The function is vectorized over expense_ratio. Share lost is 1 - fv / index_fund; then min(ratios[...]).

index_fund <- future_value_after_fees(300, 0.07, 0.0004, 40)
ratios <- c(0, 0.0004, 0.001, 0.005, 0.01, 0.015)
fv <- future_value_after_fees(300, 0.07, ratios, 40)
min(ratios[1 - fv / index_fund > 0.10])