Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteUse 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.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →#1 Best Overall
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:
Rank #2
| 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.
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.
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.
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.
Best Value
| 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.
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
dequefromcollectionsif 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.
Quick Recap
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.




