Build a Personal Expense Tracker
Build a Python CLI app that tracks expenses and saves data to a file.
Introduction
30 Second Summary
Keeping track of where your money goes is one of those tasks that sounds simple until you actually try to do it consistently. A quick coffee here, a subscription there, and suddenly your bank balance is a mystery.
In this project, you will build a personal expense tracker using Python. You will write a command-line application that records expenses, organizes them by category, and saves everything to a file so your data sticks around between sessions.
What You'll Build
A Python command-line application that accepts user input to track, categorize, and summarize personal expenses.
By the end of this project, you'll have:
- A working expense tracker that accepts input, displays spending data in a formatted table, and shows a category breakdown.
- Expenses saved to a JSON file that persists between sessions so your data is never lost.
- A clear understanding of Python fundamentals (variables, functions, loops, dictionaries) through building a real application.
- Secret Mission: Add a monthly budget feature that warns you when you are close to or over your spending limit.
Are there any prerequisites?
No prior programming experience is needed. This project includes full setup instructions for Python and VS Code, so you will install everything from scratch in Step 1.
Install Python and VS Code
Before you can build your expense tracker, you need the programming language and a code editor to write in.
In this step, you'll install Python 3.14, VS Code (your code editor), and the Python extension that connects the two.
In this step, get ready to:
- Install Python 3.14.
- Install VS Code.
- Install the Python extension for VS Code.
Install Python via the Python install manager
Python is the programming language you'll use to build your expense tracker. Let's check if it's already installed on your machine.
- Press Win+R to open the Run dialog.
- Type cmd and press Enter to open Command Prompt.
- Check if Python is installed by running this command:
python --version
✔️ I see Python 3.14 or higher
Python is ready to go. Skip ahead to the next substep where you'll install VS Code.
ⓧ I see an older version
You have an older version of Python installed. The Python install manager will get you the latest version.
- Open the Microsoft Store app on your computer.
- Search for Python install manager and click Install.
Alternatively, install it from your existing Command Prompt by running:
winget install 9NQ7512CXL7T
- Close your current Command Prompt window.
- Open a new Command Prompt (Win+R, type cmd, press Enter).
- Run python --version again.
What does the Python install manager do?
The Python install manager automatically downloads and installs the latest Python version when you first run the python command. It handles updates going forward too.
You should now see Python 3.14 followed by a patch number (like 3.14.5). If you still see the old version, the old Python installation might be taking priority on your system PATH. Uninstall the old version from Settings > Apps, then try again.
ⓧ Command not found
Python is not installed yet. You'll install it using the Python install manager, which is the recommended approach for Windows.
- Open the Microsoft Store app on your computer.
- Search for Python install manager and click Install.
Alternatively, install it from Command Prompt by running:
winget install 9NQ7512CXL7T
- Close your current Command Prompt window.
- Open a new Command Prompt (Win+R, type cmd, press Enter).
- Run python --version to verify the installation.
What does the Python install manager do?
The first time you run the python command after installing the manager, it automatically downloads and installs the latest Python version for you.
You should see Python 3.14 followed by a patch number (like 3.14.5).
Still not seeing Python 3.14?
Make sure you opened a new Command Prompt window after installing the Python install manager. Existing windows won't pick up the new installation.
If you see a prompt asking you to install Python from the Microsoft Store instead, the install manager did not install correctly. Try the winget install 9NQ7512CXL7T method in a new Command Prompt.
help me troubleshoot my Python installation on Windows
Install VS Code
VS Code is a free code editor that gives you syntax highlighting, error detection, and a one-click run button for Python files.
- Open your browser and go to https://code.visualstudio.com.
- Click the download button and run the User setup installer.
- Follow the installer prompts, accepting the default options.
- After installation completes, close any open Command Prompt windows.
- Open a new Command Prompt (Win+R, type cmd, press Enter).
- Verify VS Code is installed by running:
code --version
✔️ I see a version number
VS Code is installed and ready. You should see a version number like 1.100.0 or similar.
ⓧ Command not found
The code command is not available in your terminal. This usually means the installer did not add VS Code to your system PATH.
- Restart your computer to ensure PATH changes take effect.
- Open a new Command Prompt and try code --version again.
- If it still does not work, re-run the VS Code installer and ensure the Add to PATH checkbox is selected.
Install the Python extension
The Python extension adds IntelliSense (code suggestions), syntax highlighting, and a run button so you can execute Python files with a single click.
- Press the Windows key to open your search bar.
- Type VS Code and press Enter to open it.
- Press Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows) to open the Extensions panel.
- Type Python in the search bar.
- Find the one by Microsoft (it shows ms-python.python as the identifier).
- Click Install.
What does the Python extension give you?
This extension adds a Run button (a play icon in the top-right corner of your editor) that lets you execute Python files without typing commands in the terminal.
It also provides IntelliSense, which shows autocomplete suggestions and catches errors as you type.
You should see the extension appear in the Extensions panel with a green checkmark or a "Disable" button, confirming it is active.
Not sure which Python extension to install?
There are several Python-related extensions in the marketplace. Look for the one with the publisher name Microsoft and the identifier ms-python.python. It typically has millions of downloads and appears first in the results.
help me find and install the correct Python extension in VS Code
Your tools are ready. Next up, you'll create your project file and write your first Python function.
Create Your Project and First Function
Your tools are installed and ready to go. Now it's time to create the actual project where your expense tracker will live.
In this step, you'll create your project folder, write your first Python function, and handle user input. By the end, you'll have a working function that collects expense data from the keyboard and stores it in a list.
In this step, get ready to:
- Create your project folder and verify Python runs in VS Code.
- Build a function that collects expense data from the user.
- Add input validation so the program handles bad data gracefully.
Create your project folder and file
Every Python project starts with a folder to keep your files organized. You'll create one now and test that VS Code can run Python code inside it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop in the dialog that appears.
- Click the New Folder button at the top of the dialog.
- Name the folder expense-tracker and press Enter.
- With expense-tracker selected, click Select Folder to open it in VS Code.
Now create your first Python file inside this folder.
- In the Explorer sidebar on the left, hover over the EXPENSE-TRACKER heading.
- Click the New File icon (a page with a plus sign).
- Type expense_tracker.py and press Enter.
Let's test that Python runs correctly in your new file.
- Type this code into expense_tracker.py:
print("Expense Tracker is running!")
- Click the Run (play) button in the top-right corner of the editor.
Where is the Run button?
The Run button is a small triangle (play icon) in the top-right corner of the editor area. It appears because the Python extension recognizes your .py file.
A terminal panel opens at the bottom of VS Code. You should see Expense Tracker is running! printed in the output.
Don't see any output?
Make sure you saved the file before clicking Run. Press Ctrl+S to save.
If you see an error about Python not being found, close and reopen VS Code so it picks up the new Python installation from Step 1.
help me fix Python not running in VS Code
Build the data structure and add_expense function
Your expense tracker needs a place to store expenses in memory. In Python, a list is perfect for holding a collection of items that grows over time.
- Select all the code in expense_tracker.py (Ctrl+A) and delete it.
- Replace it with the following code:
expenses = []
def add_expense():
global expenses
category = input("Enter category (e.g., Food, Transport, Entertainment): ")
description = input("Enter description: ")
amount = float(input("Enter amount: "))
expense = {
"category": category,
"description": description,
"amount": amount
}
expenses.append(expense)
print(f"\nExpense added: {description} - ${amount:.2f} [{category}]")
add_expense()
print(expenses)
What does this code do?
expenses = [] creates an empty list that will hold all your expense records.
def add_expense() defines a function. A function is a reusable block of code you can call by name. Inside it, input() pauses the program and waits for the user to type something.
The curly braces {} create a dictionary. A dictionary stores data as key-value pairs, like a labeled filing system. Each expense gets three labels: category, description, and amount.
The last two lines (add_expense() and print(expenses)) are temporary test code so you can see the function in action. You'll remove them shortly.
- Save expense_tracker.py with Ctrl+S.
- Click the Run button in the top-right corner.
- In the terminal, type a category (like Food), a description (like Lunch), and an amount (like 12.50) when prompted.
You should see a confirmation message followed by the list printed with your expense dictionary inside it.
Seeing a NameError or SyntaxError?
Check that your function definition starts with def add_expense(): and has a colon at the end.
Make sure all lines inside the function are indented with 4 spaces. Python uses indentation to determine which lines belong to a function.
help me fix my add_expense function
Add input validation for the amount
Right now, if someone types letters instead of a number for the amount, the program crashes. You can prevent this using a try/except block, which catches errors and handles them gracefully.
- In expense_tracker.py, find this line inside your add_expense() function:
amount = float(input("Enter amount: "))
- Replace that single line with this while loop:
while True:
try:
amount = float(input("Enter amount: "))
break
except ValueError:
print("Invalid amount. Please enter a number.")
What does this validation do?
while True creates a loop that runs forever until something breaks out of it. The try block attempts to convert the input to a number.
If the conversion works, break exits the loop and the program continues. If the user types something that isn't a number, Python raises a ValueError, which the except block catches and shows a helpful message. Then the loop repeats.
Now remove the temporary test lines at the bottom of your file.
- Scroll to the bottom of expense_tracker.py and find these two lines:
add_expense()
print(expenses)
- Delete both lines.
- Save expense_tracker.py with Ctrl+S.
- Click the Run button to test the validation. When prompted for an amount, type abc and press Enter.
You should see the message Invalid amount. Please enter a number. and the program asks for the amount again instead of crashing.
Program still crashes on bad input?
Make sure the while True: line is indented at the same level as category = input(...) above it (4 spaces).
The try: and except ValueError: lines should be indented 8 spaces (inside both the function and the while loop).
help me fix my input validation loop
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete expense_tracker.py file at the end of this step:
expenses = []
def add_expense():
global expenses
category = input("Enter category (e.g., Food, Transport, Entertainment): ")
description = input("Enter description: ")
while True:
try:
amount = float(input("Enter amount: "))
break
except ValueError:
print("Invalid amount. Please enter a number.")
expense = {
"category": category,
"description": description,
"amount": amount
}
expenses.append(expense)
print(f"\nExpense added: {description} - ${amount:.2f} [{category}]")
Your first function is working and handles bad input gracefully. Next up, you'll write functions to display your expenses in a formatted table and show spending totals by category.
Display Expenses and Category Summary
In the previous step, you built the add_expense() function that collects and stores expense data. Now you have a way to put data in, but no way to see it.
In this step, you will write two display functions. One shows all your expenses in a neat table, and the other breaks down spending by category so you can see where your money goes.
In this step, get ready to:
- Write a function that displays all expenses in a formatted table.
- Write a function that summarizes total spending by category.
Write the view_expenses function
When you add expenses, you need a way to review them. The view_expenses() function will print every expense in a formatted table with numbered rows, column headers, and a total at the bottom.
- In expense_tracker.py, find the last line of your add_expense() function:
print(f"\nExpense added: {description} - ${amount:.2f} [{category}]")
- Below that line, add the following view_expenses() function:
def view_expenses():
# Handle the case where no expenses exist yet
if not expenses:
print("\nNo expenses recorded yet.")
return
# Print table headers with fixed-width columns
print("\n--- All Expenses ---")
print(f"{'No.':<5}{'Category':<15}{'Description':<20}{'Amount':<10}")
print("-" * 50)
# Loop through expenses with a numbered index starting at 1
for i, expense in enumerate(expenses, 1):
print(f"{i:<5}{expense['category']:<15}{expense['description']:<20}${expense['amount']:<9.2f}")
# Calculate and print the total of all expenses
total = sum(expense["amount"] for expense in expenses)
print("-" * 50)
print(f"{'Total:':<40}${total:.2f}")
What does this code do?
This function first checks whether the expenses list is empty. If it is, it prints a friendly message and exits early with return.
The :<5 syntax in f-strings is a width formatter. It pads the text to a fixed number of characters so columns line up neatly.
The enumerate(expenses, 1) function gives you both a counter and each item from the list. The 1 tells Python to start counting from 1 instead of the default 0.
You will not see output from this function yet. In the next step, you will build a menu that calls view_expenses() when you choose the right option.
Seeing a syntax error?
Make sure you have a blank line between the end of add_expense() and the start of def view_expenses():. Python uses blank lines to separate top-level function definitions.
Check that the f-string curly braces {} are typed exactly as shown. Missing or extra braces will cause an error.
help me fix a syntax error in my view_expenses function
Write the category_summary function
Seeing all expenses is useful, but knowing how much you spend per category is more powerful. The category_summary() function groups your expenses and totals each category separately.
- Below your view_expenses() function, add the category_summary() function:
def category_summary():
# Handle the case where no expenses exist yet
if not expenses:
print("\nNo expenses recorded yet.")
return
# Build a dictionary that accumulates totals per category
summary = {}
for expense in expenses:
category = expense["category"]
if category in summary:
summary[category] += expense["amount"]
else:
summary[category] = expense["amount"]
# Print the category breakdown table
print("\n--- Spending by Category ---")
print(f"{'Category':<20}{'Total':<10}")
print("-" * 30)
for category, total in summary.items():
print(f"{category:<20}${total:.2f}")
# Print the grand total across all categories
grand_total = sum(summary.values())
print("-" * 30)
print(f"{'Grand Total:':<20}${grand_total:.2f}")
What does this code do?
The summary dictionary starts empty. Each time the loop finds an expense, it checks if that category already exists as a key. If it does, the amount gets added to the running total. If not, it creates a new key with that amount as the starting value.
The .items() method lets you loop through both keys and values of a dictionary at the same time. The sum(summary.values()) call adds up all the category totals to produce the grand total.
- Save expense_tracker.py.
You will not see output from this function yet either. In the next step, you will build the menu system that ties all your functions together.
Seeing an indentation error?
Make sure def category_summary(): starts at the far left (no leading spaces). It should be at the same indentation level as def view_expenses(): above it.
Check that the code inside the function uses exactly 4 spaces for each indentation level. Mixing tabs and spaces causes Python to throw an error.
help me fix an indentation error in my category_summary function
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete expense_tracker.py file at this point. Compare yours against it:
expenses = []
def add_expense():
global expenses
category = input("Enter category (e.g., Food, Transport, Entertainment): ")
description = input("Enter description: ")
while True:
try:
amount = float(input("Enter amount: "))
break
except ValueError:
print("Invalid amount. Please enter a number.")
expense = {
"category": category,
"description": description,
"amount": amount
}
expenses.append(expense)
print(f"\nExpense added: {description} - ${amount:.2f} [{category}]")
def view_expenses():
if not expenses:
print("\nNo expenses recorded yet.")
return
print("\n--- All Expenses ---")
print(f"{'No.':<5}{'Category':<15}{'Description':<20}{'Amount':<10}")
print("-" * 50)
for i, expense in enumerate(expenses, 1):
print(f"{i:<5}{expense['category']:<15}{expense['description']:<20}${expense['amount']:<9.2f}")
total = sum(expense["amount"] for expense in expenses)
print("-" * 50)
print(f"{'Total:':<40}${total:.2f}")
def category_summary():
if not expenses:
print("\nNo expenses recorded yet.")
return
summary = {}
for expense in expenses:
category = expense["category"]
if category in summary:
summary[category] += expense["amount"]
else:
summary[category] = expense["amount"]
print("\n--- Spending by Category ---")
print(f"{'Category':<20}{'Total':<10}")
print("-" * 30)
for category, total in summary.items():
print(f"{category:<20}${total:.2f}")
grand_total = sum(summary.values())
print("-" * 30)
print(f"{'Grand Total:':<20}${grand_total:.2f}")
Your display functions are ready. Next up, you will build the menu loop that connects everything together so you can actually interact with your expense tracker.
Build the Menu Loop
Your expense tracker can add expenses, display them in a table, and summarize spending by category. The problem is that each function lives in isolation.
In this step, you'll build a menu loop that ties everything together into one interactive program. The user will see a numbered menu, pick an option, and the program keeps running until they choose to exit.
In this step, get ready to:
- Create a main() function with a menu loop.
- Connect menu choices to your existing functions.
- Add an entry point to run the program.
Create the main menu
Every interactive program needs a central loop that keeps it running. Your main() function will print a welcome banner, show a numbered menu, and wait for the user to pick an option.
- In expense_tracker.py, scroll to the bottom of your file (below the category_summary() function).
- Add the following main() function:
def main():
global expenses
expenses = load_expenses()
print("=" * 40)
print(" Personal Expense Tracker")
print("=" * 40)
while True:
print("\n--- Menu ---")
print("1. Add Expense")
print("2. View Expenses")
print("3. Category Summary")
print("4. Exit")
choice = input("\nChoose an option (1-4): ")
if choice == "1":
add_expense()
elif choice == "2":
view_expenses()
elif choice == "3":
category_summary()
elif choice == "4":
print("\nGoodbye! Your expenses have been saved.")
break
else:
print("\nInvalid choice. Please enter 1, 2, 3, or 4.")
What does this code do?
- global expenses tells Python that this function should modify the expenses list defined at the top of your file, not create a new local one.
- expenses = load_expenses() will load any previously saved expenses from a file. You will build this function in the next step.
- while True: creates an infinite loop. The program keeps showing the menu until the user picks option 4, which triggers break to exit the loop.
- choice = input(...) pauses the program and waits for the user to type a number.
Connect menu choices to functions
The if/elif/else block inside the loop is what connects the user's typed number to the functions you built in previous steps.
How do the choices map to functions?
- If the user types "1", the program calls add_expense() to record a new expense.
- If they type "2", it calls view_expenses() to display the formatted table.
- If they type "3", it calls category_summary() to show spending by category.
- If they type "4", the loop breaks and the program ends with a goodbye message.
- The else at the end catches anything that is not 1 through 4 and prints a helpful error message.
Notice that input() always returns a string, so you compare against "1" (a string), not 1 (an integer).
Add the entry point
Python needs to know where your program starts. The entry point tells Python to run main() when you execute the file directly.
- At the very bottom of expense_tracker.py (below the main() function), add the entry point by copying this code:
if __name__ == "__main__":
main()
What is __name__ == "__main__"?
When you run a Python file directly, Python sets a special variable called __name__ to "__main__". This check ensures your program only starts when you run the file on purpose, not when it gets imported by another script.
- Before testing, temporarily change the line expenses = load_expenses() inside main() to expenses = []. You will build the load_expenses() function in the next step.
- Save expense_tracker.py.
- Click the Run (play) button in the top-right corner of VS Code to run your program.
You should see the welcome banner and the numbered menu in your terminal. Try typing 1 to add an expense, 2 to view it, 3 for the summary, and 4 to exit.
- Once you have confirmed the menu works, change expenses = [] back to expenses = load_expenses() inside main(). The next step will create this function.
- Save expense_tracker.py.
Menu not showing up?
- If you see a NameError mentioning load_expenses, make sure you temporarily replaced that line with expenses = [] for testing.
- Check that if __name__ == "__main__": is at the very bottom of the file with no indentation.
- Make sure main() below it is indented with exactly four spaces.
Help me debug why my menu loop isn't running.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file with expenses = load_expenses() restored inside main().
ⓧ I'd like to double check the full code
expenses = []
def add_expense():
global expenses
category = input("Enter category (e.g., Food, Transport, Entertainment): ")
description = input("Enter description: ")
while True:
try:
amount = float(input("Enter amount: "))
break
except ValueError:
print("Invalid amount. Please enter a number.")
expense = {
"category": category,
"description": description,
"amount": amount
}
expenses.append(expense)
print(f"\nExpense added: {description} - ${amount:.2f} [{category}]")
def view_expenses():
if not expenses:
print("\nNo expenses recorded yet.")
return
print("\n--- All Expenses ---")
print(f"{'No.':<5}{'Category':<15}{'Description':<20}{'Amount':<10}")
print("-" * 50)
for i, expense in enumerate(expenses, 1):
print(f"{i:<5}{expense['category']:<15}{expense['description']:<20}${expense['amount']:<9.2f}")
total = sum(expense["amount"] for expense in expenses)
print("-" * 50)
print(f"{'Total:':<40}${total:.2f}")
def category_summary():
if not expenses:
print("\nNo expenses recorded yet.")
return
summary = {}
for expense in expenses:
category = expense["category"]
if category in summary:
summary[category] += expense["amount"]
else:
summary[category] = expense["amount"]
print("\n--- Spending by Category ---")
print(f"{'Category':<20}{'Total':<10}")
print("-" * 30)
for category, total in summary.items():
print(f"{category:<20}${total:.2f}")
grand_total = sum(summary.values())
print("-" * 30)
print(f"{'Grand Total:':<20}${grand_total:.2f}")
def main():
global expenses
expenses = load_expenses()
print("=" * 40)
print(" Personal Expense Tracker")
print("=" * 40)
while True:
print("\n--- Menu ---")
print("1. Add Expense")
print("2. View Expenses")
print("3. Category Summary")
print("4. Exit")
choice = input("\nChoose an option (1-4): ")
if choice == "1":
add_expense()
elif choice == "2":
view_expenses()
elif choice == "3":
category_summary()
elif choice == "4":
print("\nGoodbye! Your expenses have been saved.")
break
else:
print("\nInvalid choice. Please enter 1, 2, 3, or 4.")
if __name__ == "__main__":
main()
Your expense tracker is now a fully interactive program. Next up, you will add file saving so your expenses stick around between sessions.
Save and Load Data with JSON
Your expense tracker has a working menu, three useful features, and solid input validation. There's one major gap though. Every time you close the program, all your data vanishes.
In this step, you'll add data persistence using Python's built-in json module. Your expenses will save to a file automatically, so they're still there the next time you run the tracker.
In this step, get ready to:
- Write a save function that stores expenses to a JSON file.
- Write a load function that reads expenses back from the file.
- Test that your data persists between program sessions.
Import json and write the save function
Python's JSON module converts Python data structures (like lists and dictionaries) into text that can be written to a file. This means your expenses list can be saved as a .json file and read back later.
- In expense_tracker.py, add the following code at the very top of the file (above expenses = []):
import json
- Now add the save_expenses() function below expenses = [] and above add_expense(). Leave a blank line between expenses = [] and the new function:
def save_expenses():
# Open the file in write mode and save expenses as JSON
with open("expenses.json", "w") as file:
json.dump(expenses, file, indent=2)
What does this code do?
open("expenses.json", "w") creates (or overwrites) a file called expenses.json in your project folder. The "w" means write mode.
json.dump(expenses, file, indent=2) converts your Python list into JSON text and writes it to the file. The indent=2 makes the output human-readable with nice formatting.
Now you need to call this function every time a new expense is added. That way the file stays up to date automatically.
- In your add_expense() function, find this line:
expenses.append(expense)
print(f"\nExpense added: {description} - ${amount:.2f} [{category}]")
- Add save_expenses() between expenses.append(expense) and the print line, so it looks like this:
expenses.append(expense)
save_expenses()
print(f"\nExpense added: {description} - ${amount:.2f} [{category}]")
- Save expense_tracker.py.
- Run the program and add one expense. After exiting, check your expense-tracker folder in the VS Code Explorer sidebar. You should see a new expenses.json file has appeared.
Don't see expenses.json?
Make sure you actually added an expense through the menu (option 1) and that the program confirmed it with a message. The file only gets created when save_expenses() runs.
Check that import json is at the very top of your file. Without it, Python cannot find the json.dump() function.
help me figure out why expenses.json is not being created
Write the load function
Saving is only half the story. You also need to read that file back when the program starts. Otherwise the tracker starts with an empty list every time.
- At the top of expense_tracker.py, add import os directly below import json:
import json
import os
- Now add the load_expenses() function directly below save_expenses():
def load_expenses():
# Check if the file exists before trying to read it
if os.path.exists("expenses.json"):
with open("expenses.json", "r") as file:
return json.load(file)
return []
What does this code do?
os.path.exists("expenses.json") checks whether the file exists before trying to open it. This prevents a crash on the very first run when no file has been created yet.
json.load(file) reads the JSON text from the file and converts it back into a Python list of dictionaries. If the file does not exist, the function returns an empty list [] instead.
- Save expense_tracker.py.
Seeing a NameError for os?
Make sure import os is on its own line directly below import json at the very top of your file. Python needs the import before it can use os.path.exists().
help me fix a NameError related to os in my expense tracker
Connect loading to startup and test persistence
Your main() function already calls load_expenses() at startup. Now that the function exists, your tracker automatically loads saved data every time it runs.
- Run the program by clicking the Run button.
- Choose option 1 to add a new expense. Enter any category, description, and amount.
- Choose option 4 to exit the program.
- Run the program again.
- Choose option 2 to view expenses. You should see the expense you added in the previous session displayed in the table.
Why does this work?
When you add an expense, save_expenses() writes the full list to expenses.json. When you start the program again, load_expenses() reads that file and fills the list back up. The data survives because it lives in a file on your computer, not just in memory.
Expenses not showing up after restart?
Open expenses.json in VS Code and check that it contains your expense data. If the file is empty or missing, save_expenses() is not being called. Make sure it sits between expenses.append(expense) and the print statement in add_expense().
Also check that main() has expenses = load_expenses() near the top (it should already be there from the previous step).
help me debug why my expenses are not persisting between sessions
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the complete expense_tracker.py file. Compare it against yours:
import json
import os
expenses = []
def save_expenses():
with open("expenses.json", "w") as file:
json.dump(expenses, file, indent=2)
def load_expenses():
if os.path.exists("expenses.json"):
with open("expenses.json", "r") as file:
return json.load(file)
return []
def add_expense():
global expenses
category = input("Enter category (e.g., Food, Transport, Entertainment): ")
description = input("Enter description: ")
while True:
try:
amount = float(input("Enter amount: "))
break
except ValueError:
print("Invalid amount. Please enter a number.")
expense = {
"category": category,
"description": description,
"amount": amount
}
expenses.append(expense)
save_expenses()
print(f"\nExpense added: {description} - ${amount:.2f} [{category}]")
def view_expenses():
if not expenses:
print("\nNo expenses recorded yet.")
return
print("\n--- All Expenses ---")
print(f"{'No.':<5}{'Category':<15}{'Description':<20}{'Amount':<10}")
print("-" * 50)
for i, expense in enumerate(expenses, 1):
print(f"{i:<5}{expense['category']:<15}{expense['description']:<20}${expense['amount']:<9.2f}")
total = sum(expense["amount"] for expense in expenses)
print("-" * 50)
print(f"{'Total:':<40}${total:.2f}")
def category_summary():
if not expenses:
print("\nNo expenses recorded yet.")
return
summary = {}
for expense in expenses:
category = expense["category"]
if category in summary:
summary[category] += expense["amount"]
else:
summary[category] = expense["amount"]
print("\n--- Spending by Category ---")
print(f"{'Category':<20}{'Total':<10}")
print("-" * 30)
for category, total in summary.items():
print(f"{category:<20}${total:.2f}")
grand_total = sum(summary.values())
print("-" * 30)
print(f"{'Grand Total:':<20}${grand_total:.2f}")
def main():
global expenses
expenses = load_expenses()
print("=" * 40)
print(" Personal Expense Tracker")
print("=" * 40)
while True:
print("\n--- Menu ---")
print("1. Add Expense")
print("2. View Expenses")
print("3. Category Summary")
print("4. Exit")
choice = input("\nChoose an option (1-4): ")
if choice == "1":
add_expense()
elif choice == "2":
view_expenses()
elif choice == "3":
category_summary()
elif choice == "4":
print("\nGoodbye! Your expenses have been saved.")
break
else:
print("\nInvalid choice. Please enter 1, 2, 3, or 4.")
if __name__ == "__main__":
main()
Why JSON instead of a database?
JSON maps directly to Python's lists and dictionaries. There is no extra library to install and no database server to run. For a personal tool like this, a simple file is the perfect fit.
Secret mission
Add a Monthly Budget Feature
Want to stay on top of your spending? Add a budget feature that warns you when you're getting close to your monthly limit. You'll create functions that save a budget to file, track your total spending, and display warnings at different thresholds.
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 and expenses.json).
- Python (installed via the Python install manager).
- VS Code with the Python extension.
Keep everything running
No action needed. Choose this if you plan to keep building Python projects or want to add more features to your expense tracker.
- Your expense-tracker folder stays where you created it.
- Python and VS Code remain installed and ready for your next project.
- Your saved expenses in expenses.json persist between sessions.
Pause - I'll come back to this later
Since this project runs locally with no background processes, there is nothing to pause. Your files sit idle on disk until you run the program again.
- Keep the expense-tracker folder where it is.
- When you are ready to return, open VS Code and select File > Open Folder to reopen the project.
Delete - I don't want to use this again
Remove all project files and optionally uninstall the tools you set up in this project.
Delete the project folder
macOS
- Open Terminal and run:
rm -rf ~/Desktop/expense-tracker
Windows
- Open Command Prompt and run:
rmdir /s /q "%USERPROFILE%\Desktop\expense-tracker"
If you created the folder in a different location, navigate there and delete it manually.
Uninstall Python (optional)
- Open Settings on your computer.
- Select Apps, then Installed apps.
- Search for Python and uninstall both the Python install manager and any Python versions it installed.
Uninstall VS Code (optional)
- Open Settings on your computer.
- Select Apps, then Installed apps.
- Search for Visual Studio Code and click Uninstall.
Nice Work!
Nice Work!
Congratulations! You just built your first real Python application from scratch.
You've learned how to:
- Built a complete command-line application that accepts user input and responds with formatted output.
- Organized data using lists, dictionaries, and reusable functions to keep your code clean and maintainable.
- Saved and loaded data with JSON so your expenses persist between sessions.
- Added budget tracking with threshold warnings that alert you when spending gets close to your limit.
Ready to quiz yourself?