October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk6 min

Master Python Collections by Building a Personal Expense Tracker

Build a personal expense tracker to see when Python lists, dictionaries, sets, and tuples are useful—and how to total and save currency values safely.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a list to keep transactions in order, a dict to describe each transaction and total spending by category, a set when you need unique categories, and a tuple for a fixed group of values. This small Python expense tracker shows how those collections work together—and how to handle money and save the records without mixing up their roles.

How do lists and dictionaries fit an expense tracker?

An expense tracker needs an ordered collection of transactions, and each transaction needs named fields. In Python, a list of dictionaries is a straightforward starting point:

expenses = [
    {
        "date": "2026-10-04",
        "category": "food",
        "description": "lunch",
        "amount": "12.34",
    }
]

The outer list preserves the sequence in which records are stored and allows duplicates: two lunches on the same date are still two separate transactions. Each inner dict maps a field name such as "category" to its value. This makes records readable and lets you add another expense with append():

expenses.append({
    "date": "2026-10-04",
    "category": "transport",
    "description": "bus fare",
    "amount": "2.50",
})

for expense in expenses:
    print(expense["date"], expense["description"], expense["amount"])

Dictionary keys are unique within a record, so a key cannot appear twice as two independent fields. In current Python, dictionaries retain insertion order; that guarantee became part of the language specification in Python 3.7. For an expense record, though, use field names to access values rather than relying on their display order.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Validate fields before using them

Direct access such as expense["amount"] raises KeyError if that key is absent. If missing fields are expected, check them explicitly or use get():

required = {"date", "category", "description", "amount"}

for expense in expenses:
    missing = required - expense.keys()
    if missing:
        raise ValueError(f"Missing expense fields: {sorted(missing)}")

    if not expense["category"].strip():
        raise ValueError("Category cannot be empty")
    if not expense["amount"].strip():
        raise ValueError("Amount cannot be empty")

Use direct subscripting when the field is required and a missing value should be treated as an error. Use expense.get("notes", "") for an optional field with a sensible default. Validation here checks presence and non-empty text; a real tracker should also validate date format and reject invalid or negative amounts according to its own rules.

What is the difference between list, tuple, set, and dictionary?

These built-in collections differ in order, mutability, and whether duplicates are allowed. The stable Python 3.14.8 documentation describes their core behavior as follows:

Collection Order Mutable? Duplicates Expense-tracker role
list Sequence order Yes Allowed Ordered transaction history; append new records
dict Insertion order in current Python Yes Keys are unique Named fields in a record; category-to-total mapping
set Unordered Yes Elements are unique Unique categories and membership checks
tuple Sequence order No Allowed A fixed group of values

The Python Software Foundation’s Python tutorial, Data Structures, puts the key set property plainly: “A set is an unordered collection with no duplicate elements.” That makes a set useful for questions like whether a category has appeared before, but unsuitable when you need to display transactions in a deliberate order.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a set for uniqueness, not display order

Build a unique category collection from the records, then sort it if you want consistent alphabetical output:

categories = {expense["category"] for expense in expenses}
for category in sorted(categories):
    print(category)

The set itself does not promise a stable display order. Sorting makes the output order explicit.

Use a tuple only for a fixed group

A tuple is an immutable sequence. It can represent a fixed pair or group, such as a year and month: period = (2026, 10). A tuple can also be a dictionary key if all its members are hashable. For an expense with named, editable fields, a dictionary is generally clearer than a positional tuple such as (date, category, description, amount), where the meaning of each position must be remembered.

How do I calculate totals by category?

Use a dictionary whose keys are category names and whose values are running totals. Keep the input amount as a decimal string, then convert it to Decimal for arithmetic. Python’s Decimal documentation explains that values such as 1.1 and 2.2 do not have exact binary floating-point representations; Decimal is designed for decimal arithmetic and is preferred for accounting applications that require strict equality invariants.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from decimal import Decimal

totals = {}
for expense in expenses:
    category = expense["category"]
    amount = Decimal(expense["amount"])
    totals[category] = totals.get(category, Decimal("0")) + amount

for category in sorted(totals):
    print(category, totals[category])

totals.get(category, Decimal("0")) supplies a zero value when a category has no total yet. This avoids a missing-key error while keeping the running total in the same numeric type as the amount.

Make currency rounding explicit

Construct Decimal values from strings, not from float literals: Decimal("12.34") preserves the written decimal value, while a float may already contain a binary approximation before conversion. Decide what rounding policy applies to your currency and calculations. To display two fractional digits using the usual half-even rounding mode, for example:

from decimal import Decimal, ROUND_HALF_EVEN

cents = Decimal("0.01")
display_total = totals["food"].quantize(cents, rounding=ROUND_HALF_EVEN)
print(display_total)

quantize() makes the number of decimal places explicit. The example selects half-even rounding; another application may require a different policy. If every entry is already constrained to cents and only addition is performed, totals will generally remain at two decimal places, but a declared policy is still important when calculations introduce fractions of a cent.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How should I save expense data to CSV or JSON?

Choose a format based on the shape and intended use of the data. CSV works well for flat, tabular transaction rows and spreadsheet use; JSON is convenient for structured data. Neither file format automatically provides privacy, encryption, backups, or safe multi-user access.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Format Good fit Python standard-library support
CSV Flat records that fit columns and rows csv.DictReader reads rows as dictionaries
JSON Structured data, including nested values json encodes and decodes JSON data

Write and read CSV rows

The standard-library CSV module provides DictWriter and DictReader, which map naturally to a list of transaction dictionaries:

import csv

fieldnames = ["date", "category", "description", "amount"]
with open("expenses.csv", "w", newline="", encoding="utf-8") as file:
    writer = csv.DictWriter(file, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(expenses)

with open("expenses.csv", newline="", encoding="utf-8") as file:
    loaded_expenses = list(csv.DictReader(file))

CSV values are text when loaded, so convert amount to Decimal again when calculating. Opening the file with newline="" follows the CSV module’s documented file-handling pattern.

Write and read JSON

For a simple list of dictionaries, the standard-library JSON module can persist the structure directly:

import json

with open("expenses.json", "w", encoding="utf-8") as file:
    json.dump(expenses, file, indent=2)

with open("expenses.json", encoding="utf-8") as file:
    loaded_expenses = json.load(file)

As with CSV, numeric amounts stored as strings should be converted back to Decimal for arithmetic. Python’s JSON documentation notes that input and output order is preserved by default when the underlying containers are ordered; that does not make JSON a database or provide concurrent-write protection.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When should the tracker use other collection tools?

Start with the list-and-dictionary design, then add a collection only when its behavior solves a real need:

  • Use a list comprehension to create a filtered or transformed list, such as selecting food purchases: food_expenses = [e for e in expenses if e["category"] == "food"].
  • Use deque from collections if the program genuinely needs efficient additions and removals at both ends, such as a queue of pending imports. Python documents deques for fast operations at both ends; inserting or removing at the front of a list requires shifting elements and takes O(n) work.
  • Keep using a list for the transaction history when order and ordinary appends matter; do not switch to a specialized collection without a queue-like requirement.

Python 3.14.8 documentation is the stable reference used here, accessed on October 4, 2026. The same fundamental choices remain clear: sequence for history, mappings for named values and totals, sets for uniqueness, and tuples for immutable groups.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.