Build a Personal Expense Tracker
Build a CLI expense tracker in Python that saves and summarizes your spending.
Introduction
30 Second Summary
Everyone has that moment where they check their bank account and wonder where all the money went. Knowing where your spending goes is the first step to controlling it.
In this project, you will build a command-line expense tracker using Python that records your spending, organizes it by category, and saves your data between sessions.
What You'll Build
A Python command-line application that lets you add expenses, view them in a formatted table, and see spending summaries by category with persistent JSON storage.
By the end of this project, you'll have:
- A working Python script with a menu-driven interface that runs in your terminal.
- Reusable functions for adding expenses, viewing them in a formatted table, and summarizing spending by category.
- Persistent data storage using JSON so your expenses are saved between sessions.
- Secret Mission: Add a monthly budget feature that warns you when you're approaching or exceeding your spending limit.
Are there any prerequisites?
None. This project includes full setup instructions for Python and VS Code. No prior programming experience is required.
Before We Start
To build our expense tracker, we need a programming language and a code editor.
In this step, you will install Python (the language that runs our program) and VS Code (the editor where you will write your code). Then you will create your project folder and confirm everything works.
In this step, get ready to:
- Install Python 3.14 and verify it runs in your terminal.
- Install VS Code with the Python extension.
- Create your project folder and run your first Python script.
Install Python
Python is the programming language you will use throughout this project. You need version 3.14 or higher installed on your computer.
- Open a terminal to check if Python is already installed.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
- Check your Python version by running this command:
macOS
python3 --version
Windows
python --version
Look at the output in your terminal, then choose the tab below that matches what you see.
✔️ I see Python 3.14 or higher
You are all set. Python is ready to go.
ⓧ I see an older version
You have Python installed, but it is an older version. You need 3.14 or higher for this project.
macOS
- Go to python.org/downloads in your browser.
- Download the latest Python 3.14 macOS installer (the .pkg file).
- Double-click the downloaded file and follow the installer steps.
- The installer will ask for your administrator password. Enter it to continue.
After installation, close and reopen your terminal, then run python3 --version again. The python.org installer ensures its python3 takes priority over the older system version.
Windows
- Open the Microsoft Store app on your computer.
- Search for Python Install Manager and click Install.
- After it finishes, close and reopen Command Prompt.
- Run python --version again. The Python Install Manager automatically downloads the latest Python runtime when you first use the python command.
ⓧ Command not found
Python is not installed yet. Follow the instructions for your operating system below.
macOS
- Go to python.org/downloads in your browser.
- Download the latest Python 3.14 macOS installer (the .pkg file).
- Double-click the downloaded file and follow the installer steps.
- The installer will ask for your administrator password. Enter it to continue.
After installation, close and reopen your terminal, then run python3 --version to confirm. You should see Python 3.14.x in the output.
Windows
- Open the Microsoft Store app on your computer.
- Search for Python Install Manager and click Install.
- After it finishes, close and reopen Command Prompt.
- Type python and press Enter. The Python Install Manager will automatically download and install the latest Python runtime for you.
- Once installation completes, run python --version to confirm. You should see Python 3.14.x in the output.
What is the Python Install Manager?
The Python Install Manager is the recommended way to install Python on Windows as of Python 3.14. It makes the python command available globally and handles updates for you. You can install it from the Microsoft Store or from python.org/downloads.
Install VS Code
VS Code is a free code editor that gives you syntax highlighting, error detection, and a built-in terminal for running Python scripts.
✔️ I already have VS Code
Great. Skip ahead to installing the Python extension below.
ⓧ I need to install it
- Go to code.visualstudio.com in your browser.
- Click the download button for your operating system.
- Run the downloaded installer and follow the prompts to install.
- Open VS Code.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- Install the Python extension by pressing Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows) to open the Extensions panel.
- Type Python in the search bar.
- Find the extension published by Microsoft (its ID is ms-python.python).
- Click Install.
Why install the Python extension?
The Python extension adds code completion, error highlighting, and a one-click run button for Python files. Without it, VS Code treats your Python files as plain text.
Create your project folder and run your first script
Now you will create a dedicated folder for your expense tracker and write a quick test script to confirm Python and VS Code are working together.
- Move to your Desktop by running this command in your terminal:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir expense-tracker
cd expense-tracker
- Open this folder in VS Code.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the expense-tracker folder, and click Open.
- Create a new file called expense_tracker.py by clicking File then New File.
- When prompted to name it, type expense_tracker.py and press Enter.
- Add this code to the file:
print("Expense Tracker is running!")
What does this code do?
The print() function outputs text to your terminal. This single line confirms that Python can find and execute your file.
- Save the file by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Open the integrated terminal in VS Code by pressing Ctrl+` (backtick key, same on both platforms).
- Run your script with this command:
macOS
python3 expense_tracker.py
Windows
python expense_tracker.py
- You should see Expense Tracker is running! printed in the terminal.
Not seeing the output?
- Make sure you saved the file (check for the dot on the file tab indicating unsaved changes).
- Confirm your terminal is in the expense-tracker folder. Run ls (macOS) or dir (Windows) and check that expense_tracker.py appears in the list.
- On macOS, use python3 instead of python. The bare python command may not exist on macOS.
Help me debug why my Python script is not running.
✔️ Awesome, I've got everything!
Great. Double check you have saved expense_tracker.py and you are ready for the next step.
ⓧ I'd like to double check the full code
print("Expense Tracker is running!")
Your environment is ready. Next up, you will write the functions that load, save, and add expenses to your tracker.
Build the Data Layer
Your development environment is ready and your Python script runs. Now it's time to build the foundation that makes your expense tracker actually useful.
Every application needs a way to store and retrieve data. In this step, you'll create the data layer: the functions that save expenses to a file and load them back so nothing is lost between sessions.
In this step, get ready to:
- Define imports and the data file constant.
- Write functions to load and save expense data.
- Write a function that collects expense details from the user.
Set up imports and the data file path
Before writing any logic, your program needs access to Python's built-in tools for working with files and dates. You also need a single constant that tells every function where to find the saved data.
- In VS Code, open expense_tracker.py.
- Select all the existing code and delete it.
- Replace it with these imports and constant by copying this code:
import json
import os
from datetime import date
# File path where expenses will be stored
DATA_FILE = "expenses.json"
What does this code do?
- json lets you convert Python data to JSON format and back. This is how your expenses get saved to a file in a structured way.
- os provides file system utilities. You'll use it to check whether the data file already exists.
- date from the datetime module gives you today's date to timestamp each expense.
- DATA_FILE is a constant that stores the filename. Defining it once means every function uses the same path.
- Save expense_tracker.py.
- Run the file in your terminal by running:
python expense_tracker.py
You should see no output and no errors. This confirms your imports are valid.
Seeing an error?
- Check that you deleted the old print("Expense Tracker is running!") line completely. Leftover fragments cause syntax errors.
- Make sure from datetime import date is on its own line. A missing line break between imports triggers an error.
- Confirm you're running the command from inside the expense-tracker folder.
Why is my expense_tracker.py throwing an import error?
Write the load and save functions
Your tracker needs two complementary functions. One reads saved expenses from disk. The other writes updated expenses back.
- In expense_tracker.py, add the load_expenses function below the DATA_FILE line by copying this code:
def load_expenses():
# Return an empty list if no data file exists yet
if not os.path.exists(DATA_FILE):
return []
# Try reading the file, handle corrupted or unreadable files
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return []
What does this code do?
- os.path.exists(DATA_FILE) checks whether expenses.json exists on disk. If not, the function returns an empty list immediately.
- The try/except block attempts to open and read the file. If the file is corrupted or unreadable, it catches the error and returns an empty list instead of crashing.
- json.load(f) converts the JSON text back into a Python list of dictionaries.
You won't see a change yet. The next chunk adds save_expenses which pairs with this function.
- Add the save_expenses function below load_expenses by copying this code:
def save_expenses(expenses):
# Write the full expense list to the JSON file
with open(DATA_FILE, "w") as f:
json.dump(expenses, f, indent=2)
What does this code do?
- open(DATA_FILE, "w") opens the file for writing. The "w" mode creates the file if it doesn't exist, or overwrites it if it does.
- json.dump(expenses, f, indent=2) converts the Python list into formatted JSON and writes it to the file. The indent=2 makes the file human-readable.
Why JSON instead of plain text?
JSON stores structured data with named fields, so each expense keeps its description, amount, category, and date together. A plain text file would require you to invent a custom format and write your own parser.
JSON is also a universal standard. Knowing how to read and write it is a skill you'll use in APIs, configuration files, and databases.
- Save expense_tracker.py.
- Run the file again to confirm there are no syntax errors:
python expense_tracker.py
You should see no output and no errors. Both functions are defined but nothing calls them yet.
Seeing an IndentationError?
- Python is strict about indentation. Make sure each line inside a function is indented with exactly 4 spaces.
- Check that you haven't mixed tabs and spaces. In VS Code, look at the bottom status bar. It should say "Spaces: 4".
- Ensure there's a blank line between load_expenses and save_expenses. Python expects blank lines between function definitions.
Help me fix an indentation or syntax error in my load/save functions.
Write the add_expense function
This is the function your users will interact with most. It collects a description, amount, and category from the terminal, validates the input, and saves the new expense.
- Add the add_expense function below save_expenses by copying this code:
def add_expense(expenses):
# Prompt user for expense description
description = input("Description: ").strip()
if not description:
print("Description cannot be empty.")
return
# Prompt for amount and validate it's a positive number
amount_str = input("Amount: $")
try:
amount = float(amount_str)
if amount <= 0:
print("Amount must be a positive number.")
return
except ValueError:
print("Invalid amount. Please enter a number.")
return
# Prompt for category with a default fallback
category = input("Category (e.g., Food, Transport, Entertainment): ").strip()
if not category:
category = "Uncategorized"
# Build the expense dictionary with today's date
expense = {
"description": description,
"amount": amount,
"category": category,
"date": str(date.today()),
}
# Save the new expense to the list and to disk
expenses.append(expense)
save_expenses(expenses)
print(f"Added: {description} - ${amount:.2f} [{category}]")
What does this code do?
- input().strip() prompts the user for text and removes any leading or trailing whitespace.
- The try/except ValueError block catches the case where someone types letters instead of a number for the amount.
- The expense dictionary groups all four fields (description, amount, category, date) into one structured object.
- str(date.today()) converts today's date into a string like "2026-05-19" so it can be stored in JSON.
- The f-string at the end gives immediate feedback confirming what was saved.
- Save expense_tracker.py.
- Test the full data layer by running this command in your terminal:
python -c "from expense_tracker import load_expenses, add_expense; e = load_expenses(); add_expense(e)"
- When prompted, enter a description like Coffee, an amount like 4.50, and a category like Food.
- You should see a confirmation message like Added: Coffee - $4.50 [Food].
- Confirm the data was saved by running:
cat expenses.json
You should see your expense stored as a JSON array with description, amount, category, and date fields.
Not seeing the confirmation message?
- If you see ModuleNotFoundError, make sure your terminal is inside the expense-tracker folder where expense_tracker.py lives.
- If you entered a non-numeric amount and saw "Invalid amount", that's the validation working correctly. Try again with a number.
- If you see a NameError, check that your function names match exactly: load_expenses, save_expenses, and add_expense.
Why isn't my add_expense function working when I test it?
✔️ Awesome, I've got everything!
Great. Double check you've saved expense_tracker.py before moving on.
ⓧ I'd like to double check the full code
Here's the complete expense_tracker.py at the end of this step:
import json
import os
from datetime import date
DATA_FILE = "expenses.json"
def load_expenses():
if not os.path.exists(DATA_FILE):
return []
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return []
def save_expenses(expenses):
with open(DATA_FILE, "w") as f:
json.dump(expenses, f, indent=2)
def add_expense(expenses):
description = input("Description: ").strip()
if not description:
print("Description cannot be empty.")
return
amount_str = input("Amount: $")
try:
amount = float(amount_str)
if amount <= 0:
print("Amount must be a positive number.")
return
except ValueError:
print("Invalid amount. Please enter a number.")
return
category = input("Category (e.g., Food, Transport, Entertainment): ").strip()
if not category:
category = "Uncategorized"
expense = {
"description": description,
"amount": amount,
"category": category,
"date": str(date.today()),
}
expenses.append(expense)
save_expenses(expenses)
print(f"Added: {description} - ${amount:.2f} [{category}]")
Your data layer is solid. Every expense you add now persists to disk and can be loaded back. Next up, you'll build the display functions that show your expenses in a clean, formatted table.
Build the Display and Summary Features
Your data layer is solid. You can add expenses, save them to a JSON file, and load them back.
Now you need a way to actually see that data. In this step, you'll build two display functions that turn your raw expense list into formatted tables with aligned columns, category breakdowns, and running totals.
In this step, get ready to:
- Write a function to display all expenses in a formatted table.
- Write a function to summarize spending by category.
- Test both functions to verify the formatted output.
Write the view_expenses function
When a user asks to see their expenses, they should get a clean table showing every entry. The view_expenses function reads through the expense list and prints each one in aligned columns.
- In expense_tracker.py, scroll to the bottom of the file (below the add_expense function) and add this new function:
def view_expenses(expenses):
# Handle case where no expenses exist yet
if not expenses:
print("No expenses recorded yet.")
return
# Print column headers with alignment
print(f"\n{'Date':<12} {'Description':<20} {'Category':<15} {'Amount':>8}")
print("-" * 58)
# Loop through each expense and display in columns
for expense in expenses:
print(
f"{expense['date']:<12} "
f"{expense['description']:<20} "
f"{expense['category']:<15} "
f"${expense['amount']:>7.2f}"
)
# Calculate and display the total of all expenses
total = sum(expense["amount"] for expense in expenses)
print("-" * 58)
print(f"{'Total':<49} ${total:>7.2f}")
What does this code do?
This function handles two scenarios. If the expense list is empty, it prints a friendly message and returns early.
If there are expenses, it prints a table header with four columns (Date, Description, Category, Amount), then loops through each expense and prints it in aligned columns. The :<12 syntax left-aligns text within 12 characters of width. The :>8 syntax right-aligns within 8 characters.
Finally, it uses a generator expression with sum() to calculate the grand total of all amounts and displays it at the bottom.
- Save expense_tracker.py.
You won't see any output yet because this function needs to be called. You'll test it in the last substep.
Write the summary_by_category function
Seeing every expense individually is useful, but sometimes you want the big picture. The summary_by_category function groups your expenses by category and shows how much you've spent in each one.
- Below the view_expenses function you just wrote, add this new function:
def summary_by_category(expenses):
# Handle case where no expenses exist yet
if not expenses:
print("No expenses recorded yet.")
return
# Build a dictionary to accumulate totals per category
categories = {}
for expense in expenses:
cat = expense["category"]
categories[cat] = categories.get(cat, 0) + expense["amount"]
# Print category headers
print(f"\n{'Category':<20} {'Total':>10}")
print("-" * 32)
# Display each category sorted alphabetically
for category, total in sorted(categories.items()):
print(f"{category:<20} ${total:>8.2f}")
# Calculate and display grand total across all categories
grand_total = sum(categories.values())
print("-" * 32)
print(f"{'Grand Total':<20} ${grand_total:>8.2f}")
What does this code do?
This function builds a dictionary called categories where each key is a category name and each value is the running total for that category. The .get(cat, 0) method returns 0 if the category hasn't been seen yet, preventing a KeyError.
The sorted() function arranges categories alphabetically so the output is consistent every time. Finally, sum(categories.values()) adds up all category totals to produce the grand total.
- Save expense_tracker.py.
You won't see a change yet. The next substep tests both functions together.
Test the formatted output
Both functions use f-strings with alignment specifiers to create neat columns. Before wiring up the menu, let's verify the output looks right.
How does f-string alignment work?
Inside an f-string, the colon after a value introduces a format spec. :<12 means left-align within 12 characters. :>8 means right-align within 8 characters.
For numbers, :.2f formats to exactly 2 decimal places. Combined with alignment (:>7.2f), this right-aligns the number in a 7-character-wide column with 2 decimal places. This keeps dollar amounts lined up vertically.
The "-" * 58 expression repeats a dash 58 times to create a visual separator between the header and the data rows.
- Open your terminal in VS Code by pressing Ctrl+` (backtick key, usually above Tab).
- Launch an interactive Python session that loads your file by running:
python -i expense_tracker.py
What does the -i flag do?
The -i flag tells Python to run your script and then drop you into an interactive prompt. All the functions you defined are now available to call directly.
- First, create a test expense list and try view_expenses by typing these lines at the >>> prompt:
expenses = load_expenses()
view_expenses(expenses)
If you haven't added any expenses yet, you should see the message "No expenses recorded yet." printed in your terminal.
- Now test the summary function by typing:
summary_by_category(expenses)
- To test with actual data, add a few expenses first by typing:
add_expense(expenses)
add_expense(expenses)
add_expense(expenses)
Enter different descriptions, amounts, and categories when prompted. Then call both display functions again to see the formatted table.
- Run both display functions again to see the formatted output:
view_expenses(expenses)
summary_by_category(expenses)
You should see your expenses in a table with aligned columns. Dates appear on the left, amounts align on the right with dollar signs, and dash separators divide the header from the data.
- Type exit() to leave the interactive session.
Seeing a NameError or ImportError?
Make sure you saved expense_tracker.py before running the python -i command. The interactive session loads whatever was last saved to disk.
If you see a NameError saying a function is not defined, check that your function definitions start at the correct indentation level (no extra spaces before def).
Help me debug my display functions
✔️ 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
import json
import os
from datetime import date
DATA_FILE = "expenses.json"
def load_expenses():
if not os.path.exists(DATA_FILE):
return []
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return []
def save_expenses(expenses):
with open(DATA_FILE, "w") as f:
json.dump(expenses, f, indent=2)
def add_expense(expenses):
description = input("Description: ").strip()
if not description:
print("Description cannot be empty.")
return
amount_str = input("Amount: $")
try:
amount = float(amount_str)
if amount <= 0:
print("Amount must be a positive number.")
return
except ValueError:
print("Invalid amount. Please enter a number.")
return
category = input("Category (e.g., Food, Transport, Entertainment): ").strip()
if not category:
category = "Uncategorized"
expense = {
"description": description,
"amount": amount,
"category": category,
"date": str(date.today()),
}
expenses.append(expense)
save_expenses(expenses)
print(f"Added: {description} - ${amount:.2f} [{category}]")
def view_expenses(expenses):
if not expenses:
print("No expenses recorded yet.")
return
print(f"\n{'Date':<12} {'Description':<20} {'Category':<15} {'Amount':>8}")
print("-" * 58)
for expense in expenses:
print(
f"{expense['date']:<12} "
f"{expense['description']:<20} "
f"{expense['category']:<15} "
f"${expense['amount']:>7.2f}"
)
total = sum(expense["amount"] for expense in expenses)
print("-" * 58)
print(f"{'Total':<49} ${total:>7.2f}")
def summary_by_category(expenses):
if not expenses:
print("No expenses recorded yet.")
return
categories = {}
for expense in expenses:
cat = expense["category"]
categories[cat] = categories.get(cat, 0) + expense["amount"]
print(f"\n{'Category':<20} {'Total':>10}")
print("-" * 32)
for category, total in sorted(categories.items()):
print(f"{category:<20} ${total:>8.2f}")
grand_total = sum(categories.values())
print("-" * 32)
print(f"{'Grand Total':<20} ${grand_total:>8.2f}")
Your display layer is complete. Next up, you'll build the menu system that ties everything together so a user can interact with your tracker without touching any code.
Wire Up the Menu and Run the Program
Your expense tracker can now display formatted tables and category summaries. But there's no way for a user to actually interact with these features yet.
In this step, you'll tie everything together with a menu system. The menu gives users a simple numbered list of actions and routes their choice to the correct function.
In this step, get ready to:
- Build a menu loop that displays options and routes user input.
- Add input validation and a Python entry point guard.
- Test the full application including data persistence.
Build the main menu loop
A menu-driven program uses a while True loop to keep showing options until the user explicitly chooses to exit. Each iteration prints the menu, reads a choice, and calls the matching function.
- In expense_tracker.py, scroll to the very bottom of the file (below the summary_by_category function).
- Add the main() function by copying the following code:
def main():
# Load any previously saved expenses from the JSON file
expenses = load_expenses()
print("=" * 40)
print(" Personal Expense Tracker")
print("=" * 40)
# Keep showing the menu until the user chooses to exit
while True:
print("\n1. Add Expense")
print("2. View Expenses")
print("3. View Summary by Category")
print("4. Exit")
choice = input("\nEnter choice (1-4): ").strip()
# Route the user's choice to the correct function
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_expenses(expenses)
elif choice == "3":
summary_by_category(expenses)
elif choice == "4":
print("Goodbye!")
break
What does this code do?
- load_expenses() runs once at startup to restore any expenses saved from a previous session.
- The while True loop runs forever until break is hit. Each pass prints the menu and waits for input.
- The if/elif chain compares the user's choice string to each option number and calls the matching function.
- When the user enters "4", break exits the loop and the program ends.
What is a while True loop?
A while True loop repeats its body indefinitely. The only way out is a break statement inside the loop.
This pattern is perfect for menus because you want the program to keep running until the user explicitly says "exit."
- Save expense_tracker.py.
Seeing an indentation error?
- Make sure the entire while True block is indented one level (4 spaces) inside def main():.
- The if/elif lines should be indented 8 spaces (two levels deep: inside the function, inside the loop).
- Check that you're using spaces, not tabs. VS Code shows this in the bottom status bar.
Help me fix my indentation error in expense_tracker.py
Add input validation and the entry point
Right now, if a user types something other than 1-4, the menu silently loops back with no feedback. You should tell the user their input was invalid.
- In your main() function, find the break line inside the elif choice == "4": block.
- Add this else clause directly below it, at the same indentation level as the if and elif lines:
else:
print("Invalid choice. Please enter 1-4.")
You also need a Python entry point guard. This tells Python to only run main() when the file is executed directly, not when it's imported as a module.
- At the very bottom of expense_tracker.py (outside and below the main() function), add the entry point guard:
if __name__ == "__main__":
main()
What is if __name__ == "__main__"?
When you run a Python file directly (like python expense_tracker.py), Python sets a special variable __name__ to "__main__".
This guard ensures the program starts only when you run the file yourself. If another script imports your file, the guard prevents main() from running automatically.
- Save expense_tracker.py.
Not sure where to put the guard?
- The if __name__ == "__main__": line must be at the leftmost indentation level (no leading spaces). It is not inside any function.
- Make sure there is a blank line between the last line of your main() function and the guard.
Help me place the if __name__ guard correctly
Test the complete application
Your expense tracker is now fully wired up. Time to give it a real test run across all features, including data persistence.
- Run your program by entering this command in the terminal:
python expense_tracker.py
You should see the program header and menu appear in your terminal.
- Enter 1 to add an expense. Enter a description like Coffee, an amount like 4.50, and a category like Food.
- Add a second expense. Try Bus pass for 2.75 in the Transport category.
- Add a third expense in the same category as the first. Try Lunch for 12.00 in Food.
- Enter 2 to view all expenses. You should see a formatted table with all three entries and a total.
- Enter 3 to view the summary. You should see Food totalling $16.50 and Transport totalling $2.75.
- Enter 7 to test invalid input. You should see the "Invalid choice. Please enter 1-4." message.
- Enter 4 to exit. You should see "Goodbye!" and the program ends.
Now test that your data persists between sessions.
- Run the program again:
python expense_tracker.py
- Enter 2 to view expenses. All three expenses from your previous session should still be there.
- Enter 4 to exit.
Why does data persist?
Every time you add an expense, save_expenses() writes the full list to expenses.json. When you restart the program, load_expenses() reads that file back into memory.
This is JSON file-based persistence. It's the simplest form of data storage and works perfectly for a single-user command-line tool.
Expenses disappeared after restarting?
- Check that expenses.json exists in your expense-tracker folder. If it's missing, the save_expenses() function may have an error.
- Make sure you're running the program from inside the expense-tracker folder. The JSON file is saved relative to where you run the command.
- Open expenses.json in VS Code to confirm it contains your expense data.
Help me debug why my expenses aren't saving 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 at this point:
import json
import os
from datetime import date
DATA_FILE = "expenses.json"
def load_expenses():
if not os.path.exists(DATA_FILE):
return []
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return []
def save_expenses(expenses):
with open(DATA_FILE, "w") as f:
json.dump(expenses, f, indent=2)
def add_expense(expenses):
description = input("Description: ").strip()
if not description:
print("Description cannot be empty.")
return
amount_str = input("Amount: $")
try:
amount = float(amount_str)
if amount <= 0:
print("Amount must be a positive number.")
return
except ValueError:
print("Invalid amount. Please enter a number.")
return
category = input("Category (e.g., Food, Transport, Entertainment): ").strip()
if not category:
category = "Uncategorized"
expense = {
"description": description,
"amount": amount,
"category": category,
"date": str(date.today()),
}
expenses.append(expense)
save_expenses(expenses)
print(f"Added: {description} - ${amount:.2f} [{category}]")
def view_expenses(expenses):
if not expenses:
print("No expenses recorded yet.")
return
print(f"\n{'Date':<12} {'Description':<20} {'Category':<15} {'Amount':>8}")
print("-" * 58)
for expense in expenses:
print(
f"{expense['date']:<12} "
f"{expense['description']:<20} "
f"{expense['category']:<15} "
f"${expense['amount']:>7.2f}"
)
total = sum(expense["amount"] for expense in expenses)
print("-" * 58)
print(f"{'Total':<49} ${total:>7.2f}")
def summary_by_category(expenses):
if not expenses:
print("No expenses recorded yet.")
return
categories = {}
for expense in expenses:
cat = expense["category"]
categories[cat] = categories.get(cat, 0) + expense["amount"]
print(f"\n{'Category':<20} {'Total':>10}")
print("-" * 32)
for category, total in sorted(categories.items()):
print(f"{category:<20} ${total:>8.2f}")
grand_total = sum(categories.values())
print("-" * 32)
print(f"{'Grand Total':<20} ${grand_total:>8.2f}")
def main():
expenses = load_expenses()
print("=" * 40)
print(" Personal Expense Tracker")
print("=" * 40)
while True:
print("\n1. Add Expense")
print("2. View Expenses")
print("3. View Summary by Category")
print("4. Exit")
choice = input("\nEnter choice (1-4): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_expenses(expenses)
elif choice == "3":
summary_by_category(expenses)
elif choice == "4":
print("Goodbye!")
break
else:
print("Invalid choice. Please enter 1-4.")
if __name__ == "__main__":
main()
Secret mission
Add a Monthly Budget Feature
Your expense tracker records spending, but it doesn't tell you when you're spending too much. Add a monthly budget system that warns you when you're approaching or exceeding your limit.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally on your machine, so there are no ongoing costs or cloud resources to worry about. Decide whether to keep your project files or remove them.
Resources you used:
- The expense-tracker project folder on your Desktop (contains expense_tracker.py and expenses.json).
Keep everything running
No action needed. Choose this if you plan to keep using your expense tracker or want to extend it further.
- Your expense-tracker folder stays on your Desktop.
- Your expenses.json file keeps all your recorded expenses and budget settings.
- Python and VS Code remain installed for future projects.
Pause - I'll come back to this later
Since this is a local project with no running processes, there is nothing to pause. Your files sit quietly on disk until you need them again.
- Close VS Code if it is still open.
- Your project folder and data file remain on your Desktop, ready for next time.
Delete - I don't want to use this again
Remove all project files from your machine. This deletes your expense data permanently.
- Close VS Code if it is still open.
- On macOS or Linux, open your terminal and run:
rm -rf ~/Desktop/expense-tracker
- On Windows, open Command Prompt or PowerShell and run:
rmdir /s /q %USERPROFILE%\Desktop\expense-tracker
- Confirm the folder is gone by checking your Desktop. You should no longer see the expense-tracker folder.
Nice Work!
Nice Work!
Nice work! You just built a fully functional Personal Expense Tracker from scratch using Python.
You've learned how to:
- Built a complete command-line application with a menu-driven interface using functions, loops, and conditionals.
- Implemented persistent data storage using JSON file I/O and error handling with try/except blocks.
- Created category-based spending summaries using dictionaries and f-string formatting.
- Added a monthly budget system with spending alerts that warn you when approaching or exceeding your limit.
Ready to quiz yourself?