Python Expense Tracker Project
1Expense Tracker Project: overview
An expense tracker stores each purchase as a dictionary inside a list, adds new entries, totals the spending per category and saves everything to a file so the data survives between runs.
Before this lesson: Python Dictionaries, Python Functions, Python File Handling, Working with JSON in Python
2Expense Tracker Project: diagram
3Expense Tracker Project: explained step by step
This project pulls lists, dictionaries, functions and files together into a tool you could really use: a small program that records what you spend and shows where the money went.
Step 1: Write down the requirements
Decide what the program must do before thinking about code:
- record an expense with a date, a category and an amount;
- show the total per category, biggest first, plus a grand total;
- remember expenses between runs by saving them to a file;
- refuse obviously wrong data, such as a negative amount.
Step 2: Choose the data structure
Every expense has the same three named fields — a job for a dictionary. Many expenses kept in order — a job for a list. So the whole data set is a list of dictionaries:
expenses = [
{"date": "2024-05-01", "category": "food", "amount": 12.50},
{"date": "2024-05-02", "category": "transport", "amount": 3.20},
]
for item in expenses:
print(item["date"], item["category"], item["amount"])Named keys make the code read like the problem (item["amount"] instead of item[2]), and this shape converts straight to JSON — and later to a pandas DataFrame.
Step 3: Split the work into functions
add_expense(expenses, date, category, amount)builds one dictionary and appends it.totals_by_category(expenses)returns a dictionary such as{"food": 21.25}.- Saving and loading write the list to a file and read it back.
- The report prints the totals in neat columns.
Each function has one job, so you can test it on its own and improve it later without breaking the others.
Step 4: Running totals with a dictionary
To add up spending per category, keep a dictionary of running totals. totals.get(category, 0) returns the total so far, or 0 the first time a category appears, and you add the new amount to it.
Step 5: Save and load with JSON
json.dump(data, file) writes the list as text, and json.load(file) rebuilds it with the same keys, strings and numbers. When the program starts, it loads the file instead of starting from an empty list.
import json
expenses = [{"date": "2024-05-01", "category": "food", "amount": 12.5}]
with open("expenses.json", "w") as file:
json.dump(expenses, file)
with open("expenses.json") as file:
print(json.load(file))On this site, files live in the browser's temporary file system and vanish when you reload the page. On your own computer, the file is created in the folder you run the script from.
Step 6: Format the report
f-strings can pad and align values: f"{name:<10}" left-aligns a name in 10 characters and f"{amount:>7.2f}" right-aligns a number with two decimals, so the columns line up.
4Expense Tracker Project: syntax
expenses = [] # list of dicts
expenses.append({"date": d, "category": c, "amount": a})
totals = {}
for row in expenses: # running totals
totals[row["category"]] = totals.get(row["category"], 0) + row["amount"]
with open("expenses.json", "w") as file: # save
json.dump(expenses, file)
with open("expenses.json") as file: # load
expenses = json.load(file)
print(f"{category:<10}{amount:>7.2f}") # aligned report line
5Expense Tracker Project: run the example
Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.
import json
def add_expense(expenses, date, category, amount):
expenses.append({"date": date, "category": category, "amount": amount})
def totals_by_category(expenses):
totals = {}
for row in expenses:
totals[row["category"]] = totals.get(row["category"], 0) + row["amount"]
return totals
expenses = []
add_expense(expenses, "2024-05-01", "food", 12.50)
add_expense(expenses, "2024-05-02", "transport", 3.20)
add_expense(expenses, "2024-05-02", "food", 8.75)
add_expense(expenses, "2024-05-03", "fun", 15.00)
with open("expenses.json", "w") as file:
json.dump(expenses, file)
with open("expenses.json") as file:
saved = json.load(file)
totals = totals_by_category(saved)
for category in sorted(totals, key=totals.get, reverse=True):
print(f"{category:<10}{totals[category]:>7.2f}")
print(f"{'TOTAL':<10}{sum(totals.values()):>7.2f}")Expected outputfood 21.25
fun 15.00
transport 3.20
TOTAL 39.45
7Expense Tracker Project: how the example works
Hover or tap a step to highlight its lines in the example above.
- Line 1The
jsonmodule saves Python lists and dictionaries as text and reads them back. - Line 3-4
add_expense()builds a dictionary for one expense and appends it to the list it is given. Lists are changed in place, so nothing needs to be returned. - Line 6-10
totals_by_category()starts with an empty dictionary. For each expense,totals.get(category, 0)fetches the running total (0 for a new category) and the amount is added to it. - Line 12-16Start with an empty list and record four expenses. Each one becomes a dictionary like
{"date": "2024-05-01", "category": "food", "amount": 12.5}. - Line 18-19Open the file in write mode (
"w") andjson.dump()writes the whole list as JSON text, replacing any older contents. - Line 20-21Open it again for reading and
json.load()rebuilds the list of dictionaries — exactly what the program would do the next time it starts. - Line 22Calculate the totals from the loaded data: food 21.25, transport 3.2 and fun 15.0.
- Line 23-24
sorted(totals, key=totals.get, reverse=True)orders the category names by their totals, biggest first.:<10pads the name to 10 characters and:>7.2fright-aligns the amount with two decimals. - Line 25The grand total is the sum of all category totals, printed in the same column layout.
8Expense Tracker Project: common mistakes
Saving with append mode instead of write mode
✗ Wrong
import json
expenses = [{"category": "food", "amount": 4.5}]
for run in range(2): # the program saves twice
with open("expenses.json", "a") as file:
json.dump(expenses, file)
with open("expenses.json") as file:
data = json.load(file)✓ Right
import json
expenses = [{"category": "food", "amount": 4.5}]
for run in range(2):
with open("expenses.json", "w") as file:
json.dump(expenses, file)
with open("expenses.json") as file:
data = json.load(file)
print(data)Mode "a" adds a second JSON list after the first, and a JSON file may hold only one value. Each save writes the whole list, so open the file with "w" to replace the old contents.
Overwriting the total instead of adding to it
✗ Wrong
expenses = [{"category": "food", "amount": 12.5},
{"category": "food", "amount": 8.75}]
totals = {}
for row in expenses:
totals[row["category"]] = row["amount"]
print(totals)✓ Right
expenses = [{"category": "food", "amount": 12.5},
{"category": "food", "amount": 8.75}]
totals = {}
for row in expenses:
totals[row["category"]] = totals.get(row["category"], 0) + row["amount"]
print(totals)Assigning replaces the old value. Read the current total with get(..., 0) and add the new amount to it.
Comparing the amount from input() as text
✗ Wrong
amount = input("Amount: ")
if amount <= 0:
print("Amount must be positive")✓ Right
amount = float(input("Amount: "))
if amount <= 0:
print("Amount must be positive")input() always returns a string, even when the user types digits. Convert it with float() before comparing or adding it up.
9Expense Tracker Project: exercises
Write your answer, then press Check. The checks run in your browser.
1. Predict the category totals
Predict the output Easy ✓ CompletedRead the code and type exactly what it prints.
expenses = [
{"category": "food", "amount": 4.5},
{"category": "books", "amount": 12.0},
{"category": "food", "amount": 3.0},
]
totals = {}
for e in expenses:
totals[e["category"]] = totals.get(e["category"], 0) + e["amount"]
print(totals)
print(len(expenses), "expenses")
print(f"food: {totals['food']:.2f}"){'food': 7.5, 'books': 12.0}
3 expenses
food: 7.50
2. Validate new expenses
Write code Medium ✓ CompletedMake add_expense() safer. It should:
- raise
ValueErrorifamountis 0 or negative; - raise
ValueErrorifcategoryis empty or only spaces; - otherwise store the category stripped and in lower case (so
" Food "is saved as"food") and append the expense.
Invalid expenses must not be added to the list.
def add_expense(expenses, date, category, amount):
expenses.append({"date": date, "category": category, "amount": amount})
expenses = []
add_expense(expenses, "2024-05-01", " Food ", 12.5)
print(expenses)raise ValueError(...) before you append.def add_expense(expenses, date, category, amount):
category = category.strip().lower()
if not category:
raise ValueError("category cannot be empty")
if amount <= 0:
raise ValueError("amount must be greater than 0")
expenses.append({"date": date, "category": category, "amount": amount})
expenses = []
add_expense(expenses, "2024-05-01", " Food ", 12.5)
print(expenses)
2 more exercises → 4 more examples
Questions learners ask
Why use a list of dictionaries instead of separate lists for dates, categories and amounts?
With separate lists, one expense is spread across three places and an insert or delete in one list can silently misalign the others. A dictionary keeps each expense's fields together under readable names, and the list keeps the expenses in order. The same shape also saves directly as JSON and loads into pandas.
Where is my expenses file saved when I run this in the browser?
In the page's temporary in-memory file system. It survives between runs on the same page but disappears when you reload or close it. Run the same code on your computer to get a real file in the folder you start Python from.
Should I store expenses in JSON or CSV?
JSON keeps numbers as numbers and handles nested data, so it loads back exactly as saved — a good default for a program's own data. CSV opens directly in a spreadsheet and is easy to share, but every value comes back as a string, so convert amounts with float() after loading.
Further reading
10Next lesson
You reached the endKeep practising with exercisesRelated:
Working with JSON in PythonPython CSV FilesPandas BasicsPython Calculator Project
← Python Calculator Project · All Beginner Projects lessons · Practise in the IDE