Capstone: build a small expense tracker
Chapter 24 of 132 · Level 1 · Beginner Python textbook · 7 real coding demonstrations + 2 worked code answers. Learn through reading, predicting and testing real Python code.
Course disclaimer · independent educational content
01 · Start here: simple explanation
Use the skills you learned to build a complete, small program instead of isolated fragments.
A capstone is a small project that combines several skills. Our expense tracker stores each transaction as a dictionary. A list collects transactions, and small functions calculate totals and summaries.
We use fixed sample data at first so every learner can reproduce the results. A later version can accept keyboard input and validate it using the techniques from Lessons 05 and 12.
We save a JSON report in the current folder. Because this sample writes a file, run it in a dedicated practice directory. The downloaded ZIP separates this example from your other work.
Do not rush: first run each section, predict the output, and inspect every variable. Then extend the completed program with your own categories and sample expenses.
02 · Learning objectives and technical words
By the end you can…
- Represent expenses as dictionaries in a list
- Calculate totals and group results
- Save a summary safely and test functions
Words you should know
- transaction
- a record of money spent or received
- report
- a formatted summary produced from data
- capstone
- a project combining several earlier skills
- refactor
- reorganizing code without changing intended behavior
03 · Point-by-point: understand the idea
Locate the input, identify the Python instruction that changes it, and compare the printed result. Make a prediction before each run so you understand what the program does—not only how to copy it.
Do not store real banking information or passwords in the beginner practice project. Use fictional names and amounts, and keep financial values approximate until you study exact-money representations.
04 · Coding examples with real outputs
Example 1: Step 1: represent one expense
Python · runnableexpense = {"category": "Food", "amount": 12.5}
print(expense["category"])
print(f"${expense['amount']:.2f}")
Food $12.50
Why it works: A dictionary gives related fields clear names.
Example 2: Step 2: store several expenses
Python · runnableexpenses = [
{"category": "Food", "amount": 12.5},
{"category": "Travel", "amount": 4.0},
{"category": "Food", "amount": 7.5},
]
print("Entries:", len(expenses))
Entries: 3
Why it works: Each list element is a dictionary representing one transaction.
Example 3: Step 3: total all expenses
Python · runnableexpenses = [12.5, 4.0, 7.5]
total = sum(expenses)
print(f"Total: ${total:.2f}")
Total: $24.00
Why it works: The built-in sum function adds the values in the list.
Example 4: Step 4: group totals by category
Python · runnableexpenses = [
{"category": "Food", "amount": 12.5},
{"category": "Travel", "amount": 4.0},
{"category": "Food", "amount": 7.5},
]
totals = {}
for item in expenses:
category = item["category"]
totals[category] = totals.get(category, 0) + item["amount"]
for category in sorted(totals):
print(f"{category}: ${totals[category]:.2f}")
Food: $20.00 Travel: $4.00
Why it works: get initializes each category at zero. sorted makes printed order deterministic.
Example 5: Step 5: write a reusable total function
Python · runnabledef calculate_total(items):
return sum(item["amount"] for item in items)
sample = [{"amount": 3.0}, {"amount": 5.5}]
print(f"${calculate_total(sample):.2f}")
$8.50
Why it works: The generator expression supplies each amount to sum without creating a separate list.
Example 6: Step 6: save and load a report
Python · runnablefrom pathlib import Path
import json
report = {"total": 24.0, "currency": "CAD"}
file = Path("expense_report.json")
file.write_text(json.dumps(report, indent=2), encoding="utf-8")
loaded = json.loads(file.read_text(encoding="utf-8"))
print(loaded["currency"], f"{loaded['total']:.2f}")
CAD 24.00
Why it works: The program reloads the saved file to verify that persistence worked.
Example 7: Step 7: verify the calculations
Python · runnabledef total_amount(items):
return sum(item["amount"] for item in items)
assert total_amount([]) == 0
assert total_amount([{"amount": 2.5}]) == 2.5
print("Expense tests passed")
Expense tests passed
Why it works: The empty-list test checks that the function handles no expenses.
05 · Exercises, debugging and worked answers
Try without looking
- Add a third category to the tracker.
- Display the largest sample expense.
- Add a function that reports whether total spending exceeds a fixed sample budget.
Worked answers
Worked solution 1: Worked answer: largest expense
Python · runnableexpenses = [4.0, 12.5, 7.5]
print(max(expenses))
12.5
Why it works: max returns the largest value in a non-empty collection.
Worked solution 2: Worked answer: sample budget
Python · runnabledef over_budget(total, budget):
return total > budget
print(over_budget(24, 20))
True
Why it works: The function returns a Boolean so the caller can choose how to display it.
06 · Build a mini-project and review
Final build: Combine the steps into a script named expense_tracker.py. Add input validation, a summary report, JSON persistence, and a minimum of five tests.
Can you explain the output before running the code? Can you change the inputs without breaking the program? Revisit the examples until you can do both.
