Build a Personal Expense Tracker
Build a CLI expense tracker in Python with persistent JSON storage.
Introduction
30 Second Summary
Keeping track of where your money goes is one of those things everyone means to do but rarely sticks with. A simple tool that records your spending and shows you patterns can change your financial awareness overnight.
In this project, you will build a personal expense tracker that runs entirely in your terminal. You'll add expenses, view them by category, see monthly summaries, and delete entries.
What You'll Build
A fully functional command-line expense tracker application built with Python's standard library that persists data between sessions.
By the end of this project, you'll have:
- A single Python file (expense_tracker.py) containing 6 functions that work together to manage your expenses.
- A JSON data file that automatically stores your expenses between sessions.
- A complete CRUD application (Create, Read, Update, Delete) using only Python's built-in modules.
- Secret Mission: Add a budget alert system that warns you when you're overspending in a category.
Are there any prerequisites?
None. This guide walks you through installing Python and setting up everything you need from scratch. You just need a computer with VS Code installed.
Before We Start
Before you can build anything, you need the right tools installed and a project folder ready to go.
In this step, you will install Python, create your project folder, and confirm everything runs correctly.
In this step, get ready to:
- Install Python 3.14.5 on your machine.
- Create the project folder and file in VS Code.
- Verify your setup by running the file in the terminal.
Download and install Python
Python is the programming language you will use to build the expense tracker. You need version 3.14.5 installed on your machine.
- Open a terminal to check whether Python is already installed.
- Press the Windows key (Windows) or Cmd+Space (macOS) to open your search bar.
- Type Command Prompt (Windows) or Terminal (macOS) and press Enter to open it.
- Check your Python version by running this command:
python --version
macOS users
On macOS, you may need to use python3 --version instead.
✔️ I see Python 3.14.5
You are all set. Skip ahead to the next substep.
ⓧ I see an older version
You have an older version of Python installed. The new installer will upgrade it for you.
- Open your browser and go to python.org/downloads.
- Click the download button for Python 3.14.5.
- Run the downloaded installer.
- On the first page of the installer, check the box labelled Add Python to PATH.
- Click Install Now.
- Wait for the installation to finish, then close the installer.
- Close your current Command Prompt and open a new one.
- Run python --version again to confirm it now shows Python 3.14.5.
Why Add Python to PATH?
Adding Python to PATH lets you run python from any terminal window without needing to type the full installation path every time.
ⓧ Command not found
Python is not installed yet. Let's install it now.
- Open your browser and go to python.org/downloads.
- Click the download button for Python 3.14.5.
- Run the downloaded installer.
- On the first page of the installer, check the box labelled Add Python to PATH.
- Click Install Now.
- Wait for the installation to finish, then close the installer.
- Open a new Command Prompt (Windows) or Terminal (macOS).
- Run python --version to confirm it shows Python 3.14.5.
Why Add Python to PATH?
Adding Python to PATH lets you run python from any terminal window without needing to type the full installation path every time.
Still seeing an older version?
Make sure you opened a new Command Prompt after installation. Existing terminal windows do not pick up PATH changes until they are reopened.
On macOS, if python3 --version still shows an older version, you may need to close and reopen Terminal after the install completes.
Help me troubleshoot my Python installation.
Create your project folder and file
Now you need a dedicated folder for your expense tracker project and a Python file inside it.
- Press the Windows key (Windows) or Cmd+Space (macOS) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop in the file dialog.
- Click New Folder (the button in the file dialog).
- Name the folder expense-tracker and press Enter.
- Select the expense-tracker folder and click Select Folder (Windows) or Open (macOS).
What is a workspace in VS Code?
When you open a folder in VS Code, it becomes your workspace. VS Code shows its contents in the Explorer sidebar on the left, making it easy to create and manage files for your project.
- In the Explorer sidebar on the left, click the New File... button (the file icon with a plus sign).
- Type expense_tracker.py and press Enter.
The file opens in the editor. It is empty, which is expected.
Don't see the Explorer sidebar?
Press Ctrl+Shift+E (Windows) or Cmd+Shift+E (macOS) to open the Explorer view.
If you see a welcome screen instead of your folder contents, go to File > Open Folder and select your expense-tracker folder again.
Help me find the Explorer sidebar in VS Code.
Verify your setup
Running your empty file confirms that Python and VS Code are connected correctly.
- Open the integrated terminal in VS Code by clicking Terminal in the top menu bar, then clicking New Terminal.
- Run your file by entering this command:
python expense_tracker.py
What should I see?
You should see absolutely nothing. The terminal returns to the prompt with no output and no errors. This means Python successfully ran your empty file.
If you see an error message, something is not connected properly. Check the troubleshooting callout below.
Seeing an error?
If you see a message about the file not being found, make sure the terminal is inside your expense-tracker folder. Look at the path shown in the terminal prompt. If it points somewhere else, VS Code opened the terminal in the wrong directory.
Close the terminal, go to File > Open Folder, select your expense-tracker folder, and open a new terminal.
Help me fix the error I see when running python expense_tracker.py.
Your environment is ready. Next up, you will write the data layer that loads and saves expenses to a file.
Install Python and Set Up Your Project
Every Python project starts with making sure you have the right version of Python installed and a clean workspace to build in.
In this step, you'll install Python 3.14.5, create your project folder in VS Code, and confirm everything runs correctly before you start writing code.
In this step, get ready to:
- Install Python 3.14.5 and verify it's working.
- Set up your project folder and create your first Python file.
- Confirm your setup by running the file.
Download and install Python
Python is the programming language you'll use to build your expense tracker. You need version 3.14.5 specifically, which is the latest stable release.
- Open your browser and go to the Python downloads page:
- https://www.python.org/downloads/
- Click the large button to download Python 3.14.5 (the Windows installer 64-bit).
- Run the downloaded installer file.
- On the first page of the installer, check the box labelled Add Python to PATH.
- Click Install Now.
Why check Add Python to PATH?
PATH is a list of folders your computer searches when you type a command. Checking this box tells Windows where to find Python so you can run it from any terminal window.
Without it, typing python in the terminal would return an error because Windows wouldn't know where Python lives on your machine.
Once the installer finishes, you need to verify that Python installed correctly.
- Press the Windows key to open your search bar.
- Type Command Prompt and press Enter to open it.
- Check your Python version by running this command:
python --version
✔️ I see Python 3.14.5
Your terminal shows Python 3.14.5. Python is installed correctly and ready to use.
ⓧ I see an older version
If you see a version number lower than 3.14.5, the new installation may not be on your PATH yet. The old version is still taking priority.
- Close your Command Prompt completely.
- Open a brand new Command Prompt (search for it again using the Windows key).
- Run python --version again.
If it still shows an older version, open Start and search for Manage app execution aliases. Make sure the python.exe alias is set to Python (default). Then try again in a fresh Command Prompt.
ⓧ Command not found
If the command is not recognized, the installer did not add Python to your PATH.
- Re-run the Python installer you downloaded.
- Select Modify.
- On the first page, make sure Add Python to PATH is checked.
- Complete the installer and close your Command Prompt.
- Open a new Command Prompt and run python --version again.
Create your project folder and file in VS Code
VS Code is where you'll write all your code. You need a dedicated folder for this project and a single Python file to hold your expense tracker.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop.
- Click New Folder and name it expense-tracker.
- Select the expense-tracker folder and click Select Folder (Windows) or Open (macOS).
VS Code now has your project folder open. Next, create the Python file.
- In the Explorer sidebar on the left, hover over the folder name and click the New File... icon (it looks like a page with a plus sign).
- Type expense_tracker.py and press Enter.
Why underscores in the filename?
Python filenames use underscores instead of hyphens. The folder is expense-tracker (with a hyphen), but the Python file is expense_tracker.py (with an underscore). Hyphens in Python filenames cause import errors.
- Confirm expense_tracker.py appears in the Explorer sidebar and is open in the editor.
Don't see the Explorer sidebar?
Press Ctrl+Shift+E (Windows) or Cmd+Shift+E (macOS) to open the Explorer view. Your expense_tracker.py file should be listed under the expense-tracker folder.
help me find the Explorer sidebar in VS Code
Run your empty file to confirm the setup
Before writing any code, confirm that VS Code can run Python files successfully. You'll use the integrated terminal, which is a command line built right into VS Code.
- Open the integrated terminal by clicking Terminal in the top menu bar, then clicking New Terminal.
- Run your file by entering this command:
python expense_tracker.py
✔️ No output, no errors
The command runs and returns you to the prompt with no output. This is correct. An empty Python file produces no output and no errors.
ⓧ I see an error
If you see an error, check these common causes:
- Make sure you saved expense_tracker.py (press Ctrl+S or Cmd+S).
- Check that the terminal is in the expense-tracker folder. The prompt should show expense-tracker in the path.
- Make sure the file is completely empty. If you accidentally typed something, delete it and save again.
What is the integrated terminal?
The integrated terminal is a command line built directly into VS Code. It automatically opens in your project folder, so you don't need to navigate there. You can toggle it open and closed with Ctrl+` (Windows) or Cmd+` (macOS).
Your environment is ready. Next up, you'll write your first Python functions to load and save expense data.
Build the Data Layer
Your Python environment is ready and your project file is in place. Now it's time to give your expense tracker a memory.
Right now, if you add an expense, it disappears the moment you close the program. You need a way to save expenses to a file and load them back when the program restarts. This is called a data layer, and you'll build it using JSON files.
In this step, get ready to:
- Write functions that load and save expenses to a JSON file.
- Verify that data persists after running your program.
Write the imports and the load function
Every Python program starts by importing the tools it needs. Your expense tracker needs three modules from Python's standard library to handle files and dates.
- In expense_tracker.py, add the following code at the top of the file:
import json
import os
from datetime import datetime
# Name of the file where expenses will be stored
DATA_FILE = "expenses.json"
def load_expenses():
# Check if the data file already exists
if os.path.exists(DATA_FILE):
with open(DATA_FILE, "r") as file:
# Read and return the JSON contents as a Python list
return json.load(file)
# If no file exists yet, return an empty list
return []
What does this code do?
The first three lines bring in tools your program needs. import is like telling Python "I want to use this toolbox." json handles reading and writing JSON files. os lets you check if files exist on your computer. datetime provides today's date (you'll use it later when recording expenses).
DATA_FILE is a constant that stores the filename in one place. If you ever want to rename the file, you only change it here.
load_expenses() is a function. A function is a reusable block of code that runs only when you call it by name. This one checks whether expenses.json exists. If it does, the function opens the file and returns its contents as a Python list. If not, it returns an empty list [] so the rest of your program always has something to work with.
- Save expense_tracker.py.
Seeing a red underline in VS Code?
If VS Code shows a warning about datetime being unused, that is expected. You will use it in the next step. The warning disappears once you write code that references it.
Help me understand why VS Code shows warnings for unused imports.
Write the save function
Loading is only half the story. You also need a function that writes your expenses list back to the file whenever something changes.
- Add the following function below load_expenses() in expense_tracker.py:
def save_expenses(expenses):
# Open the file in write mode and save the list as JSON
with open(DATA_FILE, "w") as file:
json.dump(expenses, file, indent=2)
What does this code do?
with open(DATA_FILE, "w") as file opens the file for writing. The "w" means "write mode," which creates the file if it doesn't exist or overwrites it if it does. The with keyword automatically closes the file when the block finishes, even if an error occurs.
json.dump() converts your Python list into JSON text and writes it to the file. The indent=2 parameter adds spacing so the file is easy for humans to read. Without it, everything would be crammed onto one line.
- Save expense_tracker.py.
Getting an IndentationError?
Python uses indentation (spaces) to define code blocks. Make sure the with line is indented 4 spaces inside the function, and the json.dump line is indented 8 spaces (inside both the function and the with block).
Help me fix an IndentationError in my save_expenses function.
Test your data layer
Your two functions are ready, but you haven't called them yet. Adding temporary test code at the bottom of your file lets you verify everything works before building the rest of the app.
- Add the following test code at the very bottom of expense_tracker.py, below the save_expenses() function:
# Temporary test code
sample = {"amount": 10.0, "category": "Food", "description": "lunch", "date": "2024-01-01"}
test_list = [sample]
save_expenses(test_list)
print(load_expenses())
What does this code do?
sample is a dictionary. A dictionary stores data as key-value pairs inside curly braces {}. Each key (like "amount") is paired with a value (like 10.0). Dictionaries are perfect for representing a single expense because each expense has multiple attributes.
test_list = [sample] wraps that dictionary inside a list (square brackets []). Your tracker stores multiple expenses, so the data structure is always a list of dictionaries.
save_expenses(test_list) writes the list to expenses.json. Then print(load_expenses()) reads the file back and prints the result so you can confirm the round trip worked.
- Save expense_tracker.py.
- Run the file in your integrated terminal by entering this command:
python expense_tracker.py
- You should see this output in the terminal:
[{'amount': 10.0, 'category': 'Food', 'description': 'lunch', 'date': '2024-01-01'}]
- Check the Explorer sidebar in VS Code. You should see a new file called expenses.json appear inside your expense-tracker folder.
- Click expenses.json to open it. You should see your sample expense formatted with clean indentation.
Seeing a FileNotFoundError or no output?
Make sure your terminal is in the expense-tracker folder. If you see a "No such file or directory" error, run cd expense-tracker first, then try the python command again.
If you see a NameError, double check that the test code is below both function definitions in the file. Python reads top-to-bottom, so calling a function before defining it causes an error.
Help me debug why my expense_tracker.py isn't producing any output.
✔️ Awesome, I've got everything!
Great. Double check you have saved expense_tracker.py and that expenses.json is visible in your Explorer sidebar.
ⓧ I'd like to double check the full code
Here is what expense_tracker.py should look like at this point:
import json
import os
from datetime import datetime
DATA_FILE = "expenses.json"
def load_expenses():
if os.path.exists(DATA_FILE):
with open(DATA_FILE, "r") as file:
return json.load(file)
return []
def save_expenses(expenses):
with open(DATA_FILE, "w") as file:
json.dump(expenses, file, indent=2)
# Temporary test code
sample = {"amount": 10.0, "category": "Food", "description": "lunch", "date": "2024-01-01"}
test_list = [sample]
save_expenses(test_list)
print(load_expenses())
And expenses.json should contain:
[
{
"amount": 10.0,
"category": "Food",
"description": "lunch",
"date": "2024-01-01"
}
]
Your data layer is locked in. Every expense you add from now on will survive between sessions. Next up, you'll build the functions that let you actually add and view expenses through a menu.
Add and View Expenses
In the last step, you built the data layer for your expense tracker. Your app can now save expenses to a JSON file and load them back.
Now it's time to make the app interactive. You'll write functions that let users type in expenses, view them in a formatted table, and navigate through a menu that ties everything together.
In this step, get ready to:
- Write an add_expense() function with input validation and category selection.
- Write a view_expenses() function that displays expenses in a formatted table.
- Build a main() menu loop that brings everything together.
Write the add_expense() function
The first feature your users need is the ability to record a new expense. This function will ask for an amount, let them pick a category from a numbered menu, and automatically stamp today's date on the entry.
- In expense_tracker.py, find the save_expenses() function. Below it (but above the temporary test code), add the following function:
def add_expense(expenses):
print("\n--- Add New Expense ---")
# Ask the user for a dollar amount and validate it
try:
amount = float(input("Amount spent: $"))
if amount <= 0:
print("Amount must be greater than zero.")
return
except ValueError:
print("Invalid amount. Please enter a number.")
return
# Display a numbered list of spending categories
categories = ["Food", "Transport", "Bills", "Entertainment", "Shopping", "Health", "Other"]
print("\nCategories:")
for i, category in enumerate(categories, 1):
print(f" {i}. {category}")
# Get the user's category choice by number
try:
choice = int(input("Choose a category (number): "))
if choice < 1 or choice > len(categories):
print("Invalid choice.")
return
category = categories[choice - 1]
except ValueError:
print("Invalid input. Please enter a number.")
return
# Optional description the user can skip
description = input("Description (optional, press Enter to skip): ").strip()
# Build a dictionary representing this expense
expense = {
"amount": amount,
"category": category,
"description": description,
"date": datetime.now().strftime("%Y-%m-%d")
}
# Add to the list and save to the JSON file
expenses.append(expense)
save_expenses(expenses)
print(f"\nExpense added: ${amount:.2f} in {category}")
What does this code do?
This function handles several Python concepts working together:
- input() pauses the program and waits for the user to type something. It always returns a string, so you need float() to convert it to a number.
- try/except ValueError is error handling. If the user types "abc" instead of a number, Python raises a ValueError. The except block catches it and prints a friendly message instead of crashing.
- enumerate(categories, 1) loops through the list and gives you both the number (starting at 1) and the item. Perfect for numbered menus.
- datetime.now().strftime("%Y-%m-%d") grabs today's date and formats it as a string like "2026-06-09".
- expenses.append(expense) adds the new dictionary to the end of the expenses list. Then save_expenses() writes everything to disk.
What are f-strings?
The f before a string quote (like f"text {variable}") creates a formatted string. Anything inside curly braces gets replaced with its value.
The :.2f part is a format specifier that rounds a number to 2 decimal places. So ${amount:.2f} turns 10.5 into "$10.50".
- Save expense_tracker.py.
- Run the file in your terminal by running:
python expense_tracker.py
- You should still see the test output from the previous step (the sample expense printed to the terminal). This confirms Python parsed your new function without syntax errors.
Seeing a SyntaxError?
- Check that every opening parenthesis has a matching close. The f-strings with curly braces can be tricky.
- Make sure the def add_expense(expenses): line starts at the very left edge of the file (no spaces before it).
- Verify all indentation uses spaces, not tabs. VS Code's bottom bar shows "Spaces: 4" when configured correctly.
Help me fix a SyntaxError in my add_expense function
Write the view_expenses() function
Adding expenses is only useful if you can see them. This function displays all recorded expenses in a neat, aligned table with a running total at the bottom.
- Below the add_expense() function you just wrote (but still above the temporary test code), add this function:
def view_expenses(expenses):
# Handle the case where no expenses exist yet
if not expenses:
print("\nNo expenses recorded yet.")
return
# Print a formatted table header
print("\n--- All Expenses ---")
print(f"{'No.':<5}{'Date':<12}{'Category':<15}{'Amount':<10}{'Description'}")
print("-" * 55)
# Loop through each expense and display it in aligned columns
total = 0
for i, expense in enumerate(expenses, 1):
desc = expense.get("description", "")
print(f"{i:<5}{expense['date']:<12}{expense['category']:<15}${expense['amount']:<9.2f}{desc}")
total += expense["amount"]
# Print a separator and the total
print("-" * 55)
print(f"{'Total:':<32}${total:.2f}")
What does this code do?
- if not expenses: checks if the list is empty. An empty list is "falsy" in Python, so not [] is True.
- The :<5 and :<12 inside f-strings are alignment specifiers. They pad the value with spaces so each column lines up neatly.
- The for loop visits every expense in the list. The total += expense["amount"] line adds each amount to a running total. This is called the accumulator pattern.
- expense.get("description", "") safely retrieves the description. If the key doesn't exist, it returns an empty string instead of crashing.
- Save expense_tracker.py and run it again in your terminal.
- You should see the same test output as before. No errors means Python successfully defined your new function.
Seeing an IndentationError?
- Python is strict about indentation. Every line inside the function must be indented exactly 4 spaces from the left edge.
- The lines inside the for loop need 8 spaces total (4 for being inside the function, plus 4 more for being inside the loop).
Help me fix an IndentationError in my view_expenses function
Remove test code and write the main() menu
Your two feature functions are ready, but there's no way to call them yet. You need a menu that runs in an infinite loop, waiting for the user to pick an option. This is the main() function.
- In expense_tracker.py, scroll to the bottom and find the temporary test code that starts with:
# Temporary test code
sample = {"amount": 10.0, "category": "Food", "description": "lunch", "date": "2024-01-01"}
test_list = [sample]
save_expenses(test_list)
print(load_expenses())
- Select all 5 lines of the test code and delete them.
- In the same spot (below view_expenses()), add the following main() function and the startup guard:
def main():
# Load any previously saved expenses from the JSON file
expenses = load_expenses()
# Keep showing the menu until the user exits
while True:
print("\n============================")
print(" EXPENSE TRACKER")
print("============================")
print("1. Add Expense")
print("2. View All Expenses")
print("============================")
choice = input("Choose an option (1-2): ").strip()
# Call the appropriate function based on user's choice
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_expenses(expenses)
else:
print("\nInvalid option. Please choose 1-2.")
if __name__ == "__main__":
main()
What does this code do?
- while True: creates an infinite loop. The menu keeps appearing after every action until the user manually stops the program (you'll add an Exit option in a later step).
- if/elif/else is Python's way of branching. It checks conditions in order and runs the first one that matches.
- if __name__ == "__main__": is a guard that means "only run main() when this file is executed directly." If another file ever imports your code, this prevents the menu from launching automatically.
- .strip() removes any accidental spaces the user might type before or after their choice.
- Delete the old expenses.json file from your expense-tracker folder (it still has the sample test data). You can right-click it in the VS Code Explorer sidebar and select Delete.
- Save expense_tracker.py and run it:
python expense_tracker.py
- You should see the EXPENSE TRACKER menu appear with options 1 and 2.
- Type 1 and press Enter to add an expense.
- Enter an amount like 12.50, pick a category, and add a description.
- Add 2 or 3 more expenses to build up some test data.
- Type 2 to view all expenses. You should see a formatted table with columns for number, date, category, amount, and description, plus a total at the bottom.
- Press Ctrl+C to stop the program.
- Run it again with python expense_tracker.py and choose option 2. Your expenses from before should still be there. This confirms data persistence is working.
Why does the data persist?
Every time you add an expense, save_expenses() writes the entire list to expenses.json. When you restart the program, load_expenses() reads that file back into memory.
This is why you built the data layer first. The save/load pattern means your users never lose their data, even after closing the terminal.
Menu doesn't appear or program crashes immediately?
- Make sure you deleted all 5 lines of the old test code. Leftover lines like save_expenses(test_list) will run before your menu and may cause errors.
- Check that if __name__ == "__main__": is at the very bottom of the file with no indentation. The main() call below it should be indented 4 spaces.
- If you see a NameError, confirm that add_expense and view_expenses are defined above main() in the file.
Help me debug why my expense tracker menu isn't working
✔️ Awesome, I've got everything!
Great. Double check you've saved expense_tracker.py and that your menu launches correctly when you run the file.
ⓧ I'd like to double check the full code
Here's what your complete expense_tracker.py should look like at this point:
import json
import os
from datetime import datetime
DATA_FILE = "expenses.json"
def load_expenses():
if os.path.exists(DATA_FILE):
with open(DATA_FILE, "r") as file:
return json.load(file)
return []
def save_expenses(expenses):
with open(DATA_FILE, "w") as file:
json.dump(expenses, file, indent=2)
def add_expense(expenses):
print("\n--- Add New Expense ---")
try:
amount = float(input("Amount spent: $"))
if amount <= 0:
print("Amount must be greater than zero.")
return
except ValueError:
print("Invalid amount. Please enter a number.")
return
categories = ["Food", "Transport", "Bills", "Entertainment", "Shopping", "Health", "Other"]
print("\nCategories:")
for i, category in enumerate(categories, 1):
print(f" {i}. {category}")
try:
choice = int(input("Choose a category (number): "))
if choice < 1 or choice > len(categories):
print("Invalid choice.")
return
category = categories[choice - 1]
except ValueError:
print("Invalid input. Please enter a number.")
return
description = input("Description (optional, press Enter to skip): ").strip()
expense = {
"amount": amount,
"category": category,
"description": description,
"date": datetime.now().strftime("%Y-%m-%d")
}
expenses.append(expense)
save_expenses(expenses)
print(f"\nExpense added: ${amount:.2f} in {category}")
def view_expenses(expenses):
if not expenses:
print("\nNo expenses recorded yet.")
return
print("\n--- All Expenses ---")
print(f"{'No.':<5}{'Date':<12}{'Category':<15}{'Amount':<10}{'Description'}")
print("-" * 55)
total = 0
for i, expense in enumerate(expenses, 1):
desc = expense.get("description", "")
print(f"{i:<5}{expense['date']:<12}{expense['category']:<15}${expense['amount']:<9.2f}{desc}")
total += expense["amount"]
print("-" * 55)
print(f"{'Total:':<32}${total:.2f}")
def main():
expenses = load_expenses()
while True:
print("\n============================")
print(" EXPENSE TRACKER")
print("============================")
print("1. Add Expense")
print("2. View All Expenses")
print("============================")
choice = input("Choose an option (1-2): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_expenses(expenses)
else:
print("\nInvalid option. Please choose 1-2.")
if __name__ == "__main__":
main()
Your expense tracker now has a working menu, can record expenses, and displays them in a clean table. Next up, you'll add filtering by category and a monthly spending summary so you can actually learn from your spending patterns.
Filter by Category and Monthly Summary
Your tracker can add and display expenses, but scrolling through every single entry gets overwhelming fast.
In this step, you will build two powerful analysis features: filtering expenses by category and generating a monthly spending summary. These turn raw data into actionable insights about your spending habits.
In this step, get ready to:
- Write a category filter that shows only matching expenses.
- Write a monthly summary with category breakdown and percentages.
- Connect both features to your main menu.
Write the category filter function
When you have dozens of expenses, you need a way to zoom in on just one type. The view_by_category() function will extract unique categories from your data, let you pick one, and display only the matching expenses.
- In expense_tracker.py, find the end of the view_expenses() function (the last print line with Total:).
- Below it, add the view_by_category() function by copying this code:
def view_by_category(expenses):
# Handle empty list
if not expenses:
print("\nNo expenses recorded yet.")
return
# Extract unique categories from all expenses
categories = list(set(expense["category"] for expense in expenses))
print("\n--- Filter by Category ---")
for i, category in enumerate(categories, 1):
print(f" {i}. {category}")
# Get user's category choice
try:
choice = int(input("Choose a category (number): "))
if choice < 1 or choice > len(categories):
print("Invalid choice.")
return
selected = categories[choice - 1]
except ValueError:
print("Invalid input.")
return
# Filter expenses using a list comprehension
filtered = [e for e in expenses if e["category"] == selected]
# Display filtered results
print(f"\n--- Expenses in {selected} ---")
print(f"{'No.':<5}{'Date':<12}{'Amount':<10}{'Description'}")
print("-" * 45)
total = 0
for i, expense in enumerate(filtered, 1):
desc = expense.get("description", "")
print(f"{i:<5}{expense['date']:<12}${expense['amount']:<9.2f}{desc}")
total += expense["amount"]
print("-" * 45)
print(f"{'Total:':<17}${total:.2f}")
print(f"{'Count:':<17}{len(filtered)} expense(s)")
What does this code do?
This function introduces three new Python concepts that make filtering possible.
- set() takes your list of expenses and extracts only the unique category names. If you have five "Food" expenses, set() gives you just one "Food" entry.
- The list comprehension [e for e in expenses if e["category"] == selected] builds a new list containing only expenses that match the chosen category. It is a compact way to write a filter loop in one line.
- The accumulator pattern (total = 0 followed by total += expense["amount"]) adds up all the filtered amounts to give a category total.
- Save expense_tracker.py.
You will not see a change yet because the menu does not call this function. The next substep adds another analysis function, and then you will wire both into the menu together.
Seeing a syntax error?
Make sure the def view_by_category(expenses): line starts at column 1 (no indentation). It should be at the same level as def view_expenses(expenses): above it.
Also check that there is a blank line separating it from the previous function.
help me fix a syntax error in my view_by_category function
Write the monthly summary function
Knowing your total spending is useful, but knowing where your money goes each month is even better. The monthly_summary() function filters expenses for the current month, breaks them down by category with percentages, and identifies your biggest single expense.
- Below the view_by_category() function you just added, add the monthly_summary() function by copying this code:
def monthly_summary(expenses):
# Handle empty list
if not expenses:
print("\nNo expenses recorded yet.")
return
# Get current month as "YYYY-MM" string
current_month = datetime.now().strftime("%Y-%m")
# Filter expenses whose date starts with this month
monthly = [e for e in expenses if e["date"].startswith(current_month)]
if not monthly:
print(f"\nNo expenses found for {current_month}.")
return
print(f"\n--- Monthly Summary ({current_month}) ---")
# Calculate total using sum() with a generator expression
total = sum(e["amount"] for e in monthly)
# Build a dictionary of category totals
category_totals = {}
for expense in monthly:
cat = expense["category"]
category_totals[cat] = category_totals.get(cat, 0) + expense["amount"]
print(f"\nTotal spent: ${total:.2f}")
print(f"Number of expenses: {len(monthly)}")
print(f"\nBreakdown by category:")
# Sort categories by amount (highest first) and show percentages
for category, amount in sorted(category_totals.items(), key=lambda x: x[1], reverse=True):
percentage = (amount / total) * 100
print(f" {category:<15} ${amount:.2f} ({percentage:.1f}%)")
# Find and display the single biggest expense
biggest = max(monthly, key=lambda x: x["amount"])
print(f"\nBiggest expense: ${biggest['amount']:.2f} in {biggest['category']}")
if biggest.get("description"):
print(f" Description: {biggest['description']}")
What does this code do?
This function introduces several patterns for analyzing data in Python.
- str.startswith() checks if a date string like "2026-06-09" begins with "2026-06". This is how you filter for the current month without parsing dates.
- dict.get(cat, 0) returns the current value for a category, or 0 if the category has not been seen yet. This avoids a KeyError when building the totals dictionary.
- sorted() with key=lambda x: x[1] sorts the category items by their dollar amount. A lambda is a small anonymous function written in one line. Setting reverse=True puts the highest-spending category first.
- max() with key=lambda x: x["amount"] finds the single expense with the largest amount from the monthly list.
- Save expense_tracker.py.
You still will not see a change when you run the program because neither new function is connected to the menu yet. The next substep wires everything together.
Seeing an indentation error?
The for loop inside monthly_summary() that calls sorted() is long. Make sure the entire for category, amount in sorted(...) line is on a single line with no unexpected line breaks.
help me fix an indentation error in my monthly_summary function
Connect both functions to the main menu
Both analysis functions are ready. Now you need to add menu options 3 and 4 so the user can actually reach them.
- In expense_tracker.py, scroll down to your main() function.
- Find the line that says:
print("2. View All Expenses")
print("============================")
- Replace those two lines with the following, which adds two new menu options before the closing border:
print("2. View All Expenses")
print("3. View by Category")
print("4. Monthly Summary")
print("============================")
- Next, find the input prompt line:
choice = input("Choose an option (1-2): ").strip()
- Replace it with:
choice = input("Choose an option (1-4): ").strip()
- Finally, find the else: block at the bottom of the menu:
elif choice == "2":
view_expenses(expenses)
else:
print("\nInvalid option. Please choose 1-2.")
- Replace it with this expanded version that handles all four options:
elif choice == "2":
view_expenses(expenses)
elif choice == "3":
view_by_category(expenses)
elif choice == "4":
monthly_summary(expenses)
else:
print("\nInvalid option. Please choose 1-4.")
Why small functions that each do one job?
Each menu option calls a single function. The main() function only handles the menu loop and dispatching. Each feature function only handles its own logic.
This pattern is called separation of concerns. It makes your code easier to test, debug, and extend because changing one feature cannot accidentally break another.
- Save expense_tracker.py.
- Run your program in the terminal by running:
python expense_tracker.py
- Add 2-3 expenses in different categories (for example, one in Food and one in Transport).
- Choose option 3 and select a category. You should see only the matching expenses with a total for that category.
- Choose option 4 to see your monthly summary. You should see a spending breakdown with percentages and your biggest expense highlighted.
Menu showing but options 3 and 4 not working?
Double check that your elif choice == "3": line is indented at the same level as elif choice == "2": above it. All the elif branches must align.
If you see Invalid option when choosing 3 or 4, make sure you also updated the input prompt from (1-2) to (1-4).
help me debug why options 3 and 4 in my expense tracker menu are not working
✔️ Awesome, I've got everything!
Great. Double check you have saved expense_tracker.py and that both new options work from the menu.
ⓧ I'd like to double check the full code
Here is the complete expense_tracker.py file at this point:
import json
import os
from datetime import datetime
DATA_FILE = "expenses.json"
def load_expenses():
if os.path.exists(DATA_FILE):
with open(DATA_FILE, "r") as file:
return json.load(file)
return []
def save_expenses(expenses):
with open(DATA_FILE, "w") as file:
json.dump(expenses, file, indent=2)
def add_expense(expenses):
print("\n--- Add New Expense ---")
try:
amount = float(input("Amount spent: $"))
if amount <= 0:
print("Amount must be greater than zero.")
return
except ValueError:
print("Invalid amount. Please enter a number.")
return
categories = ["Food", "Transport", "Bills", "Entertainment", "Shopping", "Health", "Other"]
print("\nCategories:")
for i, category in enumerate(categories, 1):
print(f" {i}. {category}")
try:
choice = int(input("Choose a category (number): "))
if choice < 1 or choice > len(categories):
print("Invalid choice.")
return
category = categories[choice - 1]
except ValueError:
print("Invalid input. Please enter a number.")
return
description = input("Description (optional, press Enter to skip): ").strip()
expense = {
"amount": amount,
"category": category,
"description": description,
"date": datetime.now().strftime("%Y-%m-%d")
}
expenses.append(expense)
save_expenses(expenses)
print(f"\nExpense added: ${amount:.2f} in {category}")
def view_expenses(expenses):
if not expenses:
print("\nNo expenses recorded yet.")
return
print("\n--- All Expenses ---")
print(f"{'No.':<5}{'Date':<12}{'Category':<15}{'Amount':<10}{'Description'}")
print("-" * 55)
total = 0
for i, expense in enumerate(expenses, 1):
desc = expense.get("description", "")
print(f"{i:<5}{expense['date']:<12}{expense['category']:<15}${expense['amount']:<9.2f}{desc}")
total += expense["amount"]
print("-" * 55)
print(f"{'Total:':<32}${total:.2f}")
def view_by_category(expenses):
if not expenses:
print("\nNo expenses recorded yet.")
return
categories = list(set(expense["category"] for expense in expenses))
print("\n--- Filter by Category ---")
for i, category in enumerate(categories, 1):
print(f" {i}. {category}")
try:
choice = int(input("Choose a category (number): "))
if choice < 1 or choice > len(categories):
print("Invalid choice.")
return
selected = categories[choice - 1]
except ValueError:
print("Invalid input.")
return
filtered = [e for e in expenses if e["category"] == selected]
print(f"\n--- Expenses in {selected} ---")
print(f"{'No.':<5}{'Date':<12}{'Amount':<10}{'Description'}")
print("-" * 45)
total = 0
for i, expense in enumerate(filtered, 1):
desc = expense.get("description", "")
print(f"{i:<5}{expense['date']:<12}${expense['amount']:<9.2f}{desc}")
total += expense["amount"]
print("-" * 45)
print(f"{'Total:':<17}${total:.2f}")
print(f"{'Count:':<17}{len(filtered)} expense(s)")
def monthly_summary(expenses):
if not expenses:
print("\nNo expenses recorded yet.")
return
current_month = datetime.now().strftime("%Y-%m")
monthly = [e for e in expenses if e["date"].startswith(current_month)]
if not monthly:
print(f"\nNo expenses found for {current_month}.")
return
print(f"\n--- Monthly Summary ({current_month}) ---")
total = sum(e["amount"] for e in monthly)
category_totals = {}
for expense in monthly:
cat = expense["category"]
category_totals[cat] = category_totals.get(cat, 0) + expense["amount"]
print(f"\nTotal spent: ${total:.2f}")
print(f"Number of expenses: {len(monthly)}")
print(f"\nBreakdown by category:")
for category, amount in sorted(category_totals.items(), key=lambda x: x[1], reverse=True):
percentage = (amount / total) * 100
print(f" {category:<15} ${amount:.2f} ({percentage:.1f}%)")
biggest = max(monthly, key=lambda x: x["amount"])
print(f"\nBiggest expense: ${biggest['amount']:.2f} in {biggest['category']}")
if biggest.get("description"):
print(f" Description: {biggest['description']}")
def main():
expenses = load_expenses()
while True:
print("\n============================")
print(" EXPENSE TRACKER")
print("============================")
print("1. Add Expense")
print("2. View All Expenses")
print("3. View by Category")
print("4. Monthly Summary")
print("============================")
choice = input("Choose an option (1-4): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_expenses(expenses)
elif choice == "3":
view_by_category(expenses)
elif choice == "4":
monthly_summary(expenses)
else:
print("\nInvalid option. Please choose 1-4.")
if __name__ == "__main__":
main()
Your tracker now has real analytical power. Next up, you will add the ability to delete expenses and polish the app with a proper exit flow.
Delete Expenses and Polish the App
Your expense tracker can add, view, filter, and summarize spending. But what happens when you accidentally log a duplicate or enter the wrong amount?
In this step, you'll add the ability to delete expenses and finalize your menu with a proper exit option. This completes the CRUD pattern (Create, Read, Update, Delete) that professional applications use.
In this step, get ready to:
- Write a delete function that removes expenses by number.
- Add Delete and Exit options to your main menu.
- Run a full end-to-end test of your completed application.
Write the delete_expense() function
Deleting an expense requires showing the user what exists, letting them pick one by number, and then removing it from the list. You'll reuse the view_expenses() function you already wrote to display the numbered list.
- In expense_tracker.py, scroll down and find the main() function.
- Add the following function directly above def main()::
def delete_expense(expenses):
# Handle case where there's nothing to delete
if not expenses:
print("\nNo expenses to delete.")
return
# Show all expenses so user can pick one by number
view_expenses(expenses)
# Ask which expense to remove and validate the input
try:
choice = int(input("\nEnter expense number to delete: "))
if choice < 1 or choice > len(expenses):
print("Invalid number.")
return
except ValueError:
print("Invalid input.")
return
# Remove the expense and save the updated list
removed = expenses.pop(choice - 1)
save_expenses(expenses)
print(f"\nDeleted: ${removed['amount']:.2f} in {removed['category']}")
What does this code do?
This function reuses view_expenses() to display the numbered list. This is called function reuse. Instead of copying and pasting the display code, you call the function you already wrote.
The list.pop() method removes an item at a specific position and returns it. The user enters number 1 for the first expense, but Python lists start at index 0. That is why you subtract 1: expenses.pop(choice - 1).
By storing the removed item in removed, you can show the user a confirmation of exactly what was deleted.
- Save expense_tracker.py.
Seeing a NameError?
Make sure the function is placed above def main():. Python reads your file from top to bottom, so functions must be defined before they are called.
Also check that the function name is spelled exactly delete_expense with an underscore between the two words.
Help me fix a NameError in my expense tracker
Update the main menu with Delete and Exit options
Right now your menu only goes up to option 4 and has no way to exit gracefully. You need to add the Delete option, an Exit option, and a break statement that stops the infinite loop cleanly.
- In expense_tracker.py, find your main() function.
- Replace the entire main() function with the updated version below:
def main():
expenses = load_expenses()
while True:
# Display the menu header and options
print("\n============================")
print(" EXPENSE TRACKER")
print("============================")
print("1. Add Expense")
print("2. View All Expenses")
print("3. View by Category")
print("4. Monthly Summary")
print("5. Delete Expense")
print("6. Exit")
print("============================")
# Get the user's menu choice
choice = input("Choose an option (1-6): ").strip()
# Route to the correct function based on input
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_expenses(expenses)
elif choice == "3":
view_by_category(expenses)
elif choice == "4":
monthly_summary(expenses)
elif choice == "5":
delete_expense(expenses)
elif choice == "6":
print("\nGoodbye! Keep tracking your spending.")
break
else:
print("\nInvalid option. Please choose 1-6.")
What's new in this menu?
- Option 5 calls delete_expense(expenses) to let the user remove an entry.
- Option 6 prints a goodbye message and uses break to exit the while True loop. Without break, the loop would run forever.
- The else clause catches anything that is not 1 through 6 and tells the user their input was invalid. This prevents the app from silently doing nothing on bad input.
What is break?
The break keyword immediately stops the loop it sits inside. In this case, it exits the while True loop so the program ends. Without it, the only way to stop the program would be pressing Ctrl+C in the terminal.
- Save expense_tracker.py.
- Run the program in your terminal:
python expense_tracker.py
- You should see the menu now showing options 1 through 6, with Delete Expense and Exit listed.
- Type 6 and press Enter to confirm the exit option prints the goodbye message and returns you to the terminal prompt.
Menu still showing options 1-4?
Make sure you saved the file after editing. Check that you replaced the entire main() function and did not accidentally leave the old version above or below the new one.
Help me fix my expense tracker menu
Run a full end-to-end test
Your expense tracker is feature-complete. Before calling it done, you need to test every feature together to make sure nothing breaks when the pieces interact. This is called integration testing.
- Run the program:
python expense_tracker.py
- Add 3-4 expenses across different categories (try Food, Transport, and Entertainment with different amounts).
- Choose option 2 to view all expenses. Confirm they appear in a formatted table with correct totals.
- Choose option 3 to filter by category. Pick one of the categories you used and confirm only matching expenses appear.
- Choose option 4 to see your monthly summary. Confirm the breakdown percentages add up to 100%.
- Choose option 5 to delete an expense. Pick one by number and confirm the deletion message.
- Choose option 2 again. Confirm the deleted expense is gone and the total has updated.
- Choose option 6 to exit. Confirm you see the goodbye message.
Now test that your data persists between sessions.
- Run the program again:
python expense_tracker.py
- Choose option 2 to view all expenses. Your previously added expenses (minus the deleted one) should still be there.
- Try entering invalid inputs: type abc when asked for an amount, pick a number outside the category range, and type a letter at the main menu.
- Confirm the program handles each invalid input with a clear error message and does not crash.
Why test invalid inputs?
In professional software, you never assume users will enter perfect data. The try/except blocks and range checks you wrote throughout this project are called defensive programming. They keep your program running even when inputs are unexpected.
Every Python concept you used in this project
- Functions to organize code into reusable pieces.
- Dictionaries to store each expense as key-value pairs.
- Lists to hold collections of expenses.
- Loops (for and while) to iterate and repeat.
- Conditionals (if/elif/else) to branch logic.
- Error handling (try/except) to catch bad input.
- File I/O with JSON for persistent data storage.
- String formatting (f-strings) for clean output.
- The datetime module for recording dates.
- List comprehensions for filtering data.
Program crashes on invalid input?
If entering letters at the amount prompt crashes the program, check that your add_expense() function wraps float(input(...)) in a try/except ValueError block.
If the delete function crashes, make sure int(input(...)) in delete_expense() is also wrapped in try/except.
Help me debug my expense tracker crashing on invalid input
✔️ Awesome, I've got everything!
Great. Double check you have saved expense_tracker.py and all six menu options are working.
ⓧ I'd like to double check the full code
Here is the complete expense_tracker.py file. Compare your code against this:
import json
import os
from datetime import datetime
DATA_FILE = "expenses.json"
def load_expenses():
if os.path.exists(DATA_FILE):
with open(DATA_FILE, "r") as file:
return json.load(file)
return []
def save_expenses(expenses):
with open(DATA_FILE, "w") as file:
json.dump(expenses, file, indent=2)
def add_expense(expenses):
print("\n--- Add New Expense ---")
try:
amount = float(input("Amount spent: $"))
if amount <= 0:
print("Amount must be greater than zero.")
return
except ValueError:
print("Invalid amount. Please enter a number.")
return
categories = ["Food", "Transport", "Bills", "Entertainment", "Shopping", "Health", "Other"]
print("\nCategories:")
for i, category in enumerate(categories, 1):
print(f" {i}. {category}")
try:
choice = int(input("Choose a category (number): "))
if choice < 1 or choice > len(categories):
print("Invalid choice.")
return
category = categories[choice - 1]
except ValueError:
print("Invalid input. Please enter a number.")
return
description = input("Description (optional, press Enter to skip): ").strip()
expense = {
"amount": amount,
"category": category,
"description": description,
"date": datetime.now().strftime("%Y-%m-%d")
}
expenses.append(expense)
save_expenses(expenses)
print(f"\nExpense added: ${amount:.2f} in {category}")
def view_expenses(expenses):
if not expenses:
print("\nNo expenses recorded yet.")
return
print("\n--- All Expenses ---")
print(f"{'No.':<5}{'Date':<12}{'Category':<15}{'Amount':<10}{'Description'}")
print("-" * 55)
total = 0
for i, expense in enumerate(expenses, 1):
desc = expense.get("description", "")
print(f"{i:<5}{expense['date']:<12}{expense['category']:<15}${expense['amount']:<9.2f}{desc}")
total += expense["amount"]
print("-" * 55)
print(f"{'Total:':<32}${total:.2f}")
def view_by_category(expenses):
if not expenses:
print("\nNo expenses recorded yet.")
return
categories = list(set(expense["category"] for expense in expenses))
print("\n--- Filter by Category ---")
for i, category in enumerate(categories, 1):
print(f" {i}. {category}")
try:
choice = int(input("Choose a category (number): "))
if choice < 1 or choice > len(categories):
print("Invalid choice.")
return
selected = categories[choice - 1]
except ValueError:
print("Invalid input.")
return
filtered = [e for e in expenses if e["category"] == selected]
print(f"\n--- Expenses in {selected} ---")
print(f"{'No.':<5}{'Date':<12}{'Amount':<10}{'Description'}")
print("-" * 45)
total = 0
for i, expense in enumerate(filtered, 1):
desc = expense.get("description", "")
print(f"{i:<5}{expense['date']:<12}${expense['amount']:<9.2f}{desc}")
total += expense["amount"]
print("-" * 45)
print(f"{'Total:':<17}${total:.2f}")
print(f"{'Count:':<17}{len(filtered)} expense(s)")
def monthly_summary(expenses):
if not expenses:
print("\nNo expenses recorded yet.")
return
current_month = datetime.now().strftime("%Y-%m")
monthly = [e for e in expenses if e["date"].startswith(current_month)]
if not monthly:
print(f"\nNo expenses found for {current_month}.")
return
print(f"\n--- Monthly Summary ({current_month}) ---")
total = sum(e["amount"] for e in monthly)
category_totals = {}
for expense in monthly:
cat = expense["category"]
category_totals[cat] = category_totals.get(cat, 0) + expense["amount"]
print(f"\nTotal spent: ${total:.2f}")
print(f"Number of expenses: {len(monthly)}")
print(f"\nBreakdown by category:")
for category, amount in sorted(category_totals.items(), key=lambda x: x[1], reverse=True):
percentage = (amount / total) * 100
print(f" {category:<15} ${amount:.2f} ({percentage:.1f}%)")
biggest = max(monthly, key=lambda x: x["amount"])
print(f"\nBiggest expense: ${biggest['amount']:.2f} in {biggest['category']}")
if biggest.get("description"):
print(f" Description: {biggest['description']}")
def delete_expense(expenses):
if not expenses:
print("\nNo expenses to delete.")
return
view_expenses(expenses)
try:
choice = int(input("\nEnter expense number to delete: "))
if choice < 1 or choice > len(expenses):
print("Invalid number.")
return
except ValueError:
print("Invalid input.")
return
removed = expenses.pop(choice - 1)
save_expenses(expenses)
print(f"\nDeleted: ${removed['amount']:.2f} in {removed['category']}")
def main():
expenses = load_expenses()
while True:
print("\n============================")
print(" EXPENSE TRACKER")
print("============================")
print("1. Add Expense")
print("2. View All Expenses")
print("3. View by Category")
print("4. Monthly Summary")
print("5. Delete Expense")
print("6. Exit")
print("============================")
choice = input("Choose an option (1-6): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_expenses(expenses)
elif choice == "3":
view_by_category(expenses)
elif choice == "4":
monthly_summary(expenses)
elif choice == "5":
delete_expense(expenses)
elif choice == "6":
print("\nGoodbye! Keep tracking your spending.")
break
else:
print("\nInvalid option. Please choose 1-6.")
if __name__ == "__main__":
main()
Your expense tracker is complete. You have a fully functional CLI application that creates, reads, filters, summarizes, and deletes data. Next up, there is an optional Secret Mission to push your skills further with a budget alert system.
Secret mission
Add a Budget Alert System
Your expense tracker records spending, but it doesn't warn you when you're going overboard. In this secret mission, you'll add a budget alert system that tracks spending limits per category and flags you when you're approaching or exceeding your budget.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- The expense-tracker project folder (contains expense_tracker.py, expenses.json, and budgets.json).
- Python 3.14.5 installed on your machine.
Keep everything running
No action needed. Choose this if you're still actively building or plan to extend the expense tracker with new features.
- Your project files stay in the expense-tracker folder on your Desktop.
- Your expense data persists in expenses.json and budget data in budgets.json.
- Python remains installed and available for other projects.
Pause - I'll come back to this later
Shut down any running terminal sessions to free up memory. Your files stay on disk and are ready whenever you return.
- Close the integrated terminal in VS Code if it is still running the expense tracker.
- Close the VS Code window for the expense-tracker folder.
- When you're ready to resume, reopen the folder in VS Code and run python expense_tracker.py in the terminal.
Delete - I don't want to use this again
Remove all project files and optionally uninstall Python. This cannot be undone.
Delete the project folder by running the appropriate command for your operating system.
On macOS or Linux:
cd ~/Desktop
rm -rf expense-tracker
On Windows:
cd ~\Desktop
rmdir /s /q expense-tracker
- Confirm the folder is gone by running ls (macOS/Linux) or dir (Windows) on your Desktop.
- To uninstall Python on Windows: open Settings, select Apps, then Apps & features. Search for Python 3.14.5 and click Uninstall.
- Only uninstall Python if you do not plan to use it for any other projects.
Nice Work!
Nice Work!
Nice work! You just built a fully functional command-line expense tracker in Python from scratch.
You've learned how to:
- Built a complete CRUD application (Create, Read, Update, Delete) using functions, dictionaries, lists, loops, conditionals, and error handling.
- Used JSON for persistent data storage between sessions, so your expenses survive closing and reopening the program.
- Implemented category filtering and a monthly summary with percentage breakdowns, demonstrating real data aggregation patterns used in professional software.
- Secret Mission: Added a budget alert system with multi-file data management and percentage-based status indicators (OK, WARNING, OVER BUDGET).
Ready to quiz yourself?