T

TechIdea

Ecosystem

Back to python Projects
Intermediate Level

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.md

Step-by-Step Implementation

1

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.

python
"""
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

2

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) ```

python
"""
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

3

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!") ```

python
"""
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

4

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.

python
"""
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

5

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.

python
"""
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

6

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.

python
"""
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

JSONDecodeError when file is empty.

Handle exception in load_expenses and return empty list.

Category matching is case sensitive.

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.

Growth Newsletter

Get practical AI tools, SEO tips, and growth guides weekly.

Join creators, students, and businesses scaling with TechIdea.