Build an Expense Tracker With Python
SkillVeris Team
Engineering Team

A Python expense tracker records each expense (amount, category, date, note) and stores it so you can total and analyze spending over time.
In this guide, you'll learn:
- Start with the csv module for simplicity, then graduate to SQLite when you need queries and reliability.
- Model each expense as a dictionary or dataclass with amount, category, date, and description fields.
- Use collections and simple loops to summarize totals per category and per month.
- A command-line menu loop lets users add, list, and report expenses interactively.
1What You Are Building
An expense tracker is a program that records what you spend — an amount, a category, a date, and an optional note — and reports totals so you can see where your money goes. In Python you can build one as a command-line app that stores data in a CSV file or a SQLite database.
It is a rewarding beginner-to-intermediate project because it is genuinely useful and exercises core skills: reading and writing files, structuring data, working with dates, aggregating numbers, and building an interactive loop. You can start with a dozen lines and grow it into something you actually use.
2Modeling an Expense
Every feature depends on how you represent a single expense. A dictionary works for a quick start, but a dataclass gives you named fields, type hints, and cleaner code as the project grows.
Capture at least four fields: the amount as a float, a category string, a date, and a free-text description. Store the date as an ISO string (YYYY-MM-DD) so it sorts correctly and parses easily later.
- from dataclasses import dataclass
- @dataclass
- class Expense:
- amount: float
- category: str
- date: str # ISO format, e.g. '2026-07-19'
- description: str = ''
3Storing Data With CSV
The csv module in the standard library is the simplest way to persist expenses. Each expense becomes a row, and the file opens in any spreadsheet program — a nice bonus for a personal tool.
Open the file in append mode when adding a record so you never overwrite existing data, and use csv.DictWriter so column order is explicit and headers are handled for you.
💡Pro Tip
Always pass newline='' when opening a CSV file for writing. Without it, Windows inserts blank lines between rows.
Appending a Row
DictWriter maps dictionary keys to columns, making writes readable and safe.
import csv
with open('expenses.csv', 'a', newline='') as f:
writer = csv.DictWriter(f, fieldnames=['amount','category','date','description'])
if f.tell() == 0: writer.writeheader()
writer.writerow(expense.__dict__)4Upgrading to SQLite
Once you want to filter and total data efficiently, move from CSV to SQLite. The sqlite3 module ships with Python, needs no server, and stores the whole database in a single file — ideal for a personal app.
SQL lets the database do the aggregation for you. Instead of looping through every row in Python, a single GROUP BY query returns category totals directly, which stays fast even with thousands of records.
- import sqlite3
- conn = sqlite3.connect('expenses.db')
- conn.execute('CREATE TABLE IF NOT EXISTS expenses (amount REAL, category TEXT, date TEXT, description TEXT)')
- conn.execute('INSERT INTO expenses VALUES (?,?,?,?)', (amt, cat, date, desc))
- conn.commit()
5Reporting Totals by Category
The payoff of a tracker is insight. Summarize spending per category so users see their biggest costs at a glance. With CSV you loop and accumulate into a dictionary; with SQLite you run a GROUP BY query.
You can extend the same idea to monthly reports by grouping on the year-month prefix of the date string, or filter to a date range to see this month versus last.
Category Totals in SQL
One query does the aggregation the database is built for.
rows = conn.execute('SELECT category, SUM(amount) FROM expenses GROUP BY category ORDER BY 2 DESC')
for category, total in rows:
print(f'{category:<15} {total:>10.2f}')7Best Practices
A few habits make the tracker reliable and easy to extend.
- Store dates in ISO format (YYYY-MM-DD) so they sort and parse correctly.
- Validate numeric input with try/except so bad entries never crash the app.
- Use parameterized SQL queries with ? placeholders to avoid injection and quoting bugs.
- Keep a fixed list of categories to prevent typos like 'Food' and 'food' splitting totals.
- Commit after writes and close the connection when the program exits.
8Key Takeaways
The project turns everyday fundamentals into a tool you can keep using.
- Model each expense with a dataclass: amount, category, date, description.
- Start with the csv module, then move to sqlite3 for querying and scale.
- Let SQL aggregate with GROUP BY instead of looping in Python.
- A while-loop menu with input validation makes a friendly CLI.
- ISO dates and fixed categories keep your reports clean and accurate.
9Frequently Asked Questions
Q: Should I use CSV or SQLite for an expense tracker? A: Start with CSV for its simplicity and spreadsheet compatibility. Move to SQLite once you need to filter, sort, and total large amounts of data efficiently — its GROUP BY queries do aggregation far faster than Python loops.
Q: How do I total spending by category? A: In SQLite, run SELECT category, SUM(amount) FROM expenses GROUP BY category. With CSV, loop through rows and accumulate amounts into a dictionary keyed by category.
Q: How should I store dates? A: Use ISO format strings like 2026-07-19. They sort correctly as text, parse easily with datetime, and let you group by month using the year-month prefix.
Q: How do I stop bad input from crashing the program? A: Wrap conversions like float(input(...)) in a try/except block, print a helpful message on failure, and re-prompt so a single typo never ends the session.
Related Reading
Get The Print Version
Download a PDF of this article for offline reading.
About the Publisher
SkillVeris Team
Engineering Team
Our engineering team documents real build journeys so you can learn by doing, not just reading.
View all postsRelated Posts
Never miss an update
Get the latest tutorials and guides delivered to your inbox.
No spam. Unsubscribe anytime.