Python Expense Tracker
Create a practical Expense Tracker in Python that stores transactions in a JSON file, categorizes expenses, calculates total spending, and filters records by date or category.
The Problem
This project simulates a small-scale ETL (Extract, Transform, Load) pipeline, fundamental in data engineering and fintech reporting.
Real-World Use Case
This project simulates a small-scale ETL (Extract, Transform, Load) pipeline, fundamental in data engineering and fintech reporting.
Technology Stack
Understanding of Python dictionaries and lists
Prerequisite
Familiarity with file handling (open, read, write)
Prerequisite
Knowledge of Python's json built-in module
Prerequisite
Understanding of exception handling
Prerequisite
Architecture & Design
Folder Structure
expense_tracker/
├── tracker.py
├── data.json
└── README.mdStep-by-Step Implementation
Check if data.json exists; if not, initialize an empty list.
### Step 1: Project Setup Create a folder `expense_tracker` and a file `tracker.py`. Import `json` and `os` built-in modules at the top of the file.
"""
Complete Solution Code: Python Expense Tracker
"""
import json
import os
DATA_FILE = "expenses.json"
def load_expenses() -> list:
if not os.path.exists(DATA_FILE):
return []
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except json.JSONDecodeError:
print("Warning: JSON data corrupted. Starting fresh.")
return []
def save_expenses(expenses: list):
with open(DATA_FILE, "w") as f:
json.dump(expenses, f, indent=4)
def add_expense(expenses: list):
try:
amount = float(input("Enter amount ($): "))
category = input("Enter category (e.g., Food, Transport, Utilities): ").strip()
description = input("Enter brief description: ").strip()
item = {
"amount": amount,
"category": category if category else "General",
"description": description
}
expenses.append(item)
save_expenses(expenses)
print("\n➔ Expense saved successfully!")
except ValueError:
print("\nError: Invalid amount entered.")
def view_summary(expenses: list):
if not expenses:
print("\nNo expenses recorded yet.")
return
total = sum(item["amount"] for item in expenses)
print(f"\n=== Total Spending: $ {total:.2f} ===")
categories = {}
for item in expenses:
cat = item["category"]
categories[cat] = categories.get(cat, 0.0) + item["amount"]
print("\n--- Breakdown by Category ---")
for cat, amt in categories.items():
print(f"• {cat}: $ {amt:.2f} ({(amt/total)*100:.1f}%)")
def main():
expenses = load_expenses()
while True:
print("\n===============================")
print(" Python Expense Tracker ")
print("===============================")
print("1. Add New Expense")
print("2. View Spending Summary")
print("3. List All Expenses")
print("4. Exit")
choice = input("\nEnter choice (1-4): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_summary(expenses)
elif choice == "3":
if not expenses:
print("\nNo expenses recorded.")
else:
print("\n--- All Recorded Expenses ---")
for i, item in enumerate(expenses, 1):
print(f"{i}. [{item['category']}] $ {item['amount']:.2f} - {item['description']}")
elif choice == "4":
print("Goodbye!")
break
else:
print("Invalid choice.")
if __name__ == "__main__":
main()Code Explanation
Implementation step
Display a menu: Add Expense, View Expenses, View Summary, Exit.
### Step 2: Core Logic Implementation Implement functions to load and save data from JSON. ```python import json import os DATA_FILE = "data.json" def load_data() -> list: if not os.path.exists(DATA_FILE): return [] with open(DATA_FILE, "r") as f: return json.load(f) def save_data(expenses: list): with open(DATA_FILE, "w") as f: json.dump(expenses, f, indent=4) ```
"""
Complete Solution Code: Python Expense Tracker
"""
import json
import os
DATA_FILE = "expenses.json"
def load_expenses() -> list:
if not os.path.exists(DATA_FILE):
return []
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except json.JSONDecodeError:
print("Warning: JSON data corrupted. Starting fresh.")
return []
def save_expenses(expenses: list):
with open(DATA_FILE, "w") as f:
json.dump(expenses, f, indent=4)
def add_expense(expenses: list):
try:
amount = float(input("Enter amount ($): "))
category = input("Enter category (e.g., Food, Transport, Utilities): ").strip()
description = input("Enter brief description: ").strip()
item = {
"amount": amount,
"category": category if category else "General",
"description": description
}
expenses.append(item)
save_expenses(expenses)
print("\n➔ Expense saved successfully!")
except ValueError:
print("\nError: Invalid amount entered.")
def view_summary(expenses: list):
if not expenses:
print("\nNo expenses recorded yet.")
return
total = sum(item["amount"] for item in expenses)
print(f"\n=== Total Spending: $ {total:.2f} ===")
categories = {}
for item in expenses:
cat = item["category"]
categories[cat] = categories.get(cat, 0.0) + item["amount"]
print("\n--- Breakdown by Category ---")
for cat, amt in categories.items():
print(f"• {cat}: $ {amt:.2f} ({(amt/total)*100:.1f}%)")
def main():
expenses = load_expenses()
while True:
print("\n===============================")
print(" Python Expense Tracker ")
print("===============================")
print("1. Add New Expense")
print("2. View Spending Summary")
print("3. List All Expenses")
print("4. Exit")
choice = input("\nEnter choice (1-4): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_summary(expenses)
elif choice == "3":
if not expenses:
print("\nNo expenses recorded.")
else:
print("\n--- All Recorded Expenses ---")
for i, item in enumerate(expenses, 1):
print(f"{i}. [{item['category']}] $ {item['amount']:.2f} - {item['description']}")
elif choice == "4":
print("Goodbye!")
break
else:
print("Invalid choice.")
if __name__ == "__main__":
main()Code Explanation
Implementation step
On Add Expense: prompt for amount, category, date, and description, then append to list.
### Step 3: UI & Interaction Implementation Create the console menu and report formatting logic. ```python def add_expense(expenses: list): amount = float(input("Enter amount: ")) category = input("Enter category (Food, Travel, Bills, Other): ") desc = input("Enter description: ") expenses.append({"amount": amount, "category": category, "desc": desc}) save_data(expenses) print("Expense added successfully!") ```
"""
Complete Solution Code: Python Expense Tracker
"""
import json
import os
DATA_FILE = "expenses.json"
def load_expenses() -> list:
if not os.path.exists(DATA_FILE):
return []
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except json.JSONDecodeError:
print("Warning: JSON data corrupted. Starting fresh.")
return []
def save_expenses(expenses: list):
with open(DATA_FILE, "w") as f:
json.dump(expenses, f, indent=4)
def add_expense(expenses: list):
try:
amount = float(input("Enter amount ($): "))
category = input("Enter category (e.g., Food, Transport, Utilities): ").strip()
description = input("Enter brief description: ").strip()
item = {
"amount": amount,
"category": category if category else "General",
"description": description
}
expenses.append(item)
save_expenses(expenses)
print("\n➔ Expense saved successfully!")
except ValueError:
print("\nError: Invalid amount entered.")
def view_summary(expenses: list):
if not expenses:
print("\nNo expenses recorded yet.")
return
total = sum(item["amount"] for item in expenses)
print(f"\n=== Total Spending: $ {total:.2f} ===")
categories = {}
for item in expenses:
cat = item["category"]
categories[cat] = categories.get(cat, 0.0) + item["amount"]
print("\n--- Breakdown by Category ---")
for cat, amt in categories.items():
print(f"• {cat}: $ {amt:.2f} ({(amt/total)*100:.1f}%)")
def main():
expenses = load_expenses()
while True:
print("\n===============================")
print(" Python Expense Tracker ")
print("===============================")
print("1. Add New Expense")
print("2. View Spending Summary")
print("3. List All Expenses")
print("4. Exit")
choice = input("\nEnter choice (1-4): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_summary(expenses)
elif choice == "3":
if not expenses:
print("\nNo expenses recorded.")
else:
print("\n--- All Recorded Expenses ---")
for i, item in enumerate(expenses, 1):
print(f"{i}. [{item['category']}] $ {item['amount']:.2f} - {item['description']}")
elif choice == "4":
print("Goodbye!")
break
else:
print("Invalid choice.")
if __name__ == "__main__":
main()Code Explanation
Implementation step
Save updated list to data.json using json.dump().
### Step 4: Error Handling & Edge Cases Ensure corrupted JSON files are caught gracefully and invalid numerical inputs don't crash the application.
"""
Complete Solution Code: Python Expense Tracker
"""
import json
import os
DATA_FILE = "expenses.json"
def load_expenses() -> list:
if not os.path.exists(DATA_FILE):
return []
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except json.JSONDecodeError:
print("Warning: JSON data corrupted. Starting fresh.")
return []
def save_expenses(expenses: list):
with open(DATA_FILE, "w") as f:
json.dump(expenses, f, indent=4)
def add_expense(expenses: list):
try:
amount = float(input("Enter amount ($): "))
category = input("Enter category (e.g., Food, Transport, Utilities): ").strip()
description = input("Enter brief description: ").strip()
item = {
"amount": amount,
"category": category if category else "General",
"description": description
}
expenses.append(item)
save_expenses(expenses)
print("\n➔ Expense saved successfully!")
except ValueError:
print("\nError: Invalid amount entered.")
def view_summary(expenses: list):
if not expenses:
print("\nNo expenses recorded yet.")
return
total = sum(item["amount"] for item in expenses)
print(f"\n=== Total Spending: $ {total:.2f} ===")
categories = {}
for item in expenses:
cat = item["category"]
categories[cat] = categories.get(cat, 0.0) + item["amount"]
print("\n--- Breakdown by Category ---")
for cat, amt in categories.items():
print(f"• {cat}: $ {amt:.2f} ({(amt/total)*100:.1f}%)")
def main():
expenses = load_expenses()
while True:
print("\n===============================")
print(" Python Expense Tracker ")
print("===============================")
print("1. Add New Expense")
print("2. View Spending Summary")
print("3. List All Expenses")
print("4. Exit")
choice = input("\nEnter choice (1-4): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_summary(expenses)
elif choice == "3":
if not expenses:
print("\nNo expenses recorded.")
else:
print("\n--- All Recorded Expenses ---")
for i, item in enumerate(expenses, 1):
print(f"{i}. [{item['category']}] $ {item['amount']:.2f} - {item['description']}")
elif choice == "4":
print("Goodbye!")
break
else:
print("Invalid choice.")
if __name__ == "__main__":
main()Code Explanation
Implementation step
On View Summary: sum amounts by category and display formatted totals.
### Step 4: Error Handling & Edge Cases Ensure corrupted JSON files are caught gracefully and invalid numerical inputs don't crash the application.
"""
Complete Solution Code: Python Expense Tracker
"""
import json
import os
DATA_FILE = "expenses.json"
def load_expenses() -> list:
if not os.path.exists(DATA_FILE):
return []
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except json.JSONDecodeError:
print("Warning: JSON data corrupted. Starting fresh.")
return []
def save_expenses(expenses: list):
with open(DATA_FILE, "w") as f:
json.dump(expenses, f, indent=4)
def add_expense(expenses: list):
try:
amount = float(input("Enter amount ($): "))
category = input("Enter category (e.g., Food, Transport, Utilities): ").strip()
description = input("Enter brief description: ").strip()
item = {
"amount": amount,
"category": category if category else "General",
"description": description
}
expenses.append(item)
save_expenses(expenses)
print("\n➔ Expense saved successfully!")
except ValueError:
print("\nError: Invalid amount entered.")
def view_summary(expenses: list):
if not expenses:
print("\nNo expenses recorded yet.")
return
total = sum(item["amount"] for item in expenses)
print(f"\n=== Total Spending: $ {total:.2f} ===")
categories = {}
for item in expenses:
cat = item["category"]
categories[cat] = categories.get(cat, 0.0) + item["amount"]
print("\n--- Breakdown by Category ---")
for cat, amt in categories.items():
print(f"• {cat}: $ {amt:.2f} ({(amt/total)*100:.1f}%)")
def main():
expenses = load_expenses()
while True:
print("\n===============================")
print(" Python Expense Tracker ")
print("===============================")
print("1. Add New Expense")
print("2. View Spending Summary")
print("3. List All Expenses")
print("4. Exit")
choice = input("\nEnter choice (1-4): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_summary(expenses)
elif choice == "3":
if not expenses:
print("\nNo expenses recorded.")
else:
print("\n--- All Recorded Expenses ---")
for i, item in enumerate(expenses, 1):
print(f"{i}. [{item['category']}] $ {item['amount']:.2f} - {item['description']}")
elif choice == "4":
print("Goodbye!")
break
else:
print("Invalid choice.")
if __name__ == "__main__":
main()Code Explanation
Implementation step
Loop until user chooses Exit.
### Step 4: Error Handling & Edge Cases Ensure corrupted JSON files are caught gracefully and invalid numerical inputs don't crash the application.
"""
Complete Solution Code: Python Expense Tracker
"""
import json
import os
DATA_FILE = "expenses.json"
def load_expenses() -> list:
if not os.path.exists(DATA_FILE):
return []
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except json.JSONDecodeError:
print("Warning: JSON data corrupted. Starting fresh.")
return []
def save_expenses(expenses: list):
with open(DATA_FILE, "w") as f:
json.dump(expenses, f, indent=4)
def add_expense(expenses: list):
try:
amount = float(input("Enter amount ($): "))
category = input("Enter category (e.g., Food, Transport, Utilities): ").strip()
description = input("Enter brief description: ").strip()
item = {
"amount": amount,
"category": category if category else "General",
"description": description
}
expenses.append(item)
save_expenses(expenses)
print("\n➔ Expense saved successfully!")
except ValueError:
print("\nError: Invalid amount entered.")
def view_summary(expenses: list):
if not expenses:
print("\nNo expenses recorded yet.")
return
total = sum(item["amount"] for item in expenses)
print(f"\n=== Total Spending: $ {total:.2f} ===")
categories = {}
for item in expenses:
cat = item["category"]
categories[cat] = categories.get(cat, 0.0) + item["amount"]
print("\n--- Breakdown by Category ---")
for cat, amt in categories.items():
print(f"• {cat}: $ {amt:.2f} ({(amt/total)*100:.1f}%)")
def main():
expenses = load_expenses()
while True:
print("\n===============================")
print(" Python Expense Tracker ")
print("===============================")
print("1. Add New Expense")
print("2. View Spending Summary")
print("3. List All Expenses")
print("4. Exit")
choice = input("\nEnter choice (1-4): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_summary(expenses)
elif choice == "3":
if not expenses:
print("\nNo expenses recorded.")
else:
print("\n--- All Recorded Expenses ---")
for i, item in enumerate(expenses, 1):
print(f"{i}. [{item['category']}] $ {item['amount']:.2f} - {item['description']}")
elif choice == "4":
print("Goodbye!")
break
else:
print("Invalid choice.")
if __name__ == "__main__":
main()Code Explanation
Implementation step
Common Errors
Handle exception in load_expenses and return empty list.
Use .title() on category strings before saving.
Security & Performance
Run application and choose option 1 to add a $50 Food expense.
Verify that expenses.json is created on disk with formatted JSON.
Add a $30 Transport expense and check summary breakdown percentages.
Restart script and verify data persists from disk correctly.
Add date filtering using Python datetime module.
Export summary report to a CSV file.
Add budget limits per category and alert user when exceeded.
Interview Questions
Q: Why use JSON instead of plain text?
A: JSON preserves structural data types (numbers, lists, dicts) automatically, making parsing flawless.
Q: Can multiple users access this file?
A: This script is designed for single-user desktop usage. Concurrent web usage would require a database like SQLite or MongoDB.