Skip to Content
Course content

418: Building a Personal Expense Tracker

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first sit down to build something like a personal expense tracker, the instinct is usually to get data on the screen as quickly as possible. You want to be able to add a coffee, a gym membership, and a grocery bill, and then see a total. Most people start by using a simple list of tuples or lists to store these records. It feels efficient because it's just a few lines of code.

The "List of Tuples" Trap

Imagine you start your tracker like this. You create a list called expenses, and every time you spend money, you append a tuple: ("Coffee", 4.50, "Food"). At first, this is great. You can loop through the list and sum up the second element of every tuple to get your total spend. But here is where I've seen this approach fall apart for almost every junior dev I've mentored: the moment you want to actually do something with the data, the code becomes unreadable.

# The naive way
expenses = [("Coffee", 4.50, "Food"), ("Gas", 40.00, "Transport")]

# Trying to calculate total for just 'Food'
food_total = 0
for e in expenses:
    if e[2] == "Food":
        food_total += e[1]

Look at those indices: e[2] and e[1]. To anyone else reading your code—including you, three weeks from now—those numbers are meaningless. Does index 1 represent the price or the date? Does index 2 represent the category or the vendor? If you decide to add a "Date" field to the start of your tuple, every single line of logic in your entire application breaks because all your index numbers have shifted. It's fragile, and it's a nightmare to maintain.

Structuring for Sanity with Dataclasses

The better way is to move away from generic sequences and toward structured objects. In Python, the dataclass is your best friend here. Instead of remembering that the price is at index 1, you give it a name. This transforms your data from a mystery pile of values into a set of clearly defined entities. I prefer this because it documents the code as you write it.

from dataclasses import dataclass

@dataclass
class Expense:
    description: str
    amount: float
    category: str

# Now it's explicit
expenses = [Expense("Coffee", 4.50, "Food"), Expense("Gas", 40.00, "Transport")]

# This is infinitely more readable
food_total = sum(e.amount for e in expenses if e.category == "Food")

By using a dataclass, we've decoupled the structure of the data from the order of the data. If I add a date field to the Expense class, e.amount still works perfectly. We aren't guessing anymore; we're accessing properties.

Encapsulating Logic in a Manager Class

Now, while dataclasses fix the data representation, you still have the problem of "logic leakage." If you have sum() calls and if statements scattered all over your main script, your code becomes a tangled mess of data processing. I always recommend wrapping the collection in a manager class. Think of the ExpenseTracker class as the "brain" that knows how to handle the list of Expense objects.

The trade-off here is a bit more boilerplate upfront. You have to write a class and define methods like add_expense() or get_total_by_category(). But the payoff is huge: your main program loop becomes a series of high-level commands rather than a series of math operations. You stop worrying about how the total is calculated and just ask the tracker for the result. This is the difference between writing a script that works once and building a piece of software that you can actually expand without it collapsing under its own weight.




📋 Practical Task

Implement a Category-Based Budget Alert

You have a working Expense dataclass and an ExpenseTracker class that stores a list of expenses. Your task is to enhance the ExpenseTracker by adding a budget limit feature.

Modify the ExpenseTracker class to include:

  • A dictionary called budgets that maps category names (strings) to a maximum spending limit (floats).
  • A method called set_budget(category, limit) to define or update a budget for a specific category.
  • An update to the add_expense(expense) method: whenever a new expense is added, the tracker should check if the total spending for that expense's category has now exceeded its defined budget. If it has, the method should print a warning: "Warning: Budget exceeded for [Category]! Current spend: [Total]".

Ensure your logic handles cases where a category might not have a budget set yet without crashing the program.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.