Skip to content
beginner

Build a Command-Line Expense Tracker with Python

You've learned Python's building blocks—input(), functions, dictionaries, files, loops, conditionals—but you haven't yet seen how they lock together into…

Published 2026-09-05Updated 2026-09-1216 min read
Back view of traveling couple in love wearing casual clothes walking with luggage and hugging while strolling along sidewalk together during vacation
Back view of traveling couple in love wearing casual clothes walking with luggage and hugging while strolling along sidewalk together during vacation. Photo by Andrea Piacquadio on Pexels.

You've learned Python's building blocks—input(), functions, dictionaries, files, loops, conditionals—but you haven't yet seen how they lock together into one working program. This project closes that gap. By the end, you'll have built a command-line expense tracker that accepts entries, calculates totals, and remembers your data after the program closes.

What You'll Build and Why It Matters

Here's the wall most beginners hit: you can write a for loop. You can define a function. You can open a file. But when someone says "build me a tool," the pieces don't automatically assemble themselves.

This project is where that changes.

You're going to build a small but complete program that runs in your terminal. When you start it, you'll see a menu:

Expense Tracker
1. Add an expense
2. Show total
3. Quit

You'll type 1, enter an amount like 24.50 and a category like groceries, and the program stores it. Type 2 and you'll see your running total. Type 3 and the program exits—but here's the important part: next time you run it, your expenses are still there.

That last feature is what turns a toy script into a real tool. A program that forgets everything when it closes is only useful for the few seconds it runs. A program that saves its data can actually help you track spending over days or weeks.

To build this, you'll combine skills you already have:

  • input() to get choices and data from the user
  • Functions to keep the logic organized
  • Dictionaries to store each expense
  • A list to hold all expenses
  • A while loop to keep the menu alive
  • if/elif statements to route user choices
  • File I/O to save and load your data

No GUI, no database, no web interface. Just core Python and a terminal window. That's intentional: every concept here is something you've already met in isolation. The real lesson is how they fit together.

Plan the Program Before You Code

Before writing a single line, let's think about what this program must do every time it runs.

Three jobs:

  1. Show a menu so the user knows their options.
  2. Handle the user's choice—add an expense, show the total, or quit.
  3. Keep expenses in memory and on disk so data survives a restart.

The first two jobs are straightforward. The third is where most of the design thinking happens.

The main loop

A command-line program like this doesn't run once and stop. It needs to keep asking the user what they want to do until the user says "quit." That's what a while loop is for.

The shape looks like this:

while True:
    # show the menu
    # get the user's choice
    # if choice is "quit": break
    # otherwise: do the chosen action

The while True keeps the program alive. The break statement is the exit door—the only way out.

The data shape

Every expense needs two pieces of information: an amount and a category. A dictionary is the natural fit:

{"amount": 24.50, "category": "groceries"}

And since you'll have many expenses, you store them in a list:

expenses = [
    {"amount": 24.50, "category": "groceries"},
    {"amount": 12.00, "category": "transport"},
]

Deciding this before you code prevents most of the confusion that follows. When you know exactly what one expense looks like, every function you write has a clear job: create one, store one, or add up all of them.

Knowledge check

Check your understanding

Answer this question before you continue.

Which structure represents the tracker’s collection of expenses?
Single Choice

Focus: Identify the data structure used to represent multiple expenses in the tracker.

The storage format

Your expenses need to survive a restart. That means writing them to a file. The simplest readable format is JSON—JavaScript Object Notation—which Python handles with a built-in module called json.

Why JSON? Because it stores data in a text format that looks almost identical to Python dictionaries and lists. You can open the file and read it yourself. You can also see exactly what your program saved, which makes debugging far easier.

[{"amount": 24.5, "category": "groceries"}]

That's the whole plan. A list of dictionaries, saved as JSON, loaded when the program starts.

Set Up Your Project File

Let's get your environment ready. You'll need a folder for this project and a file called expense_tracker.py.

Open your terminal, create a folder, and move into it:

mkdir expense-tracker
cd expense-tracker

Now create the Python file. You can use any editor you like—VS Code, PyCharm, or even a plain text editor. The file name matters less than the .py extension.

Before we build anything real, let's confirm your setup works. Put this single line in expense_tracker.py:

print("Expense tracker starting...")

Now run it from your terminal:

python expense_tracker.py

You should see:

Expense tracker starting...

If you see that output, your setup is solid. If you get an error like python: command not found, you may need to use python3 instead:

python3 expense_tracker.py

This tiny check matters more than it looks. You're verifying that Python can find and run a saved script file—not just execute lines in the interactive shell. From here on, every change you make will be to this file, and you'll run it the same way each time.

Build the Menu Loop

Now let's replace that print statement with the program's skeleton: the menu loop.

while True:
    print("\nExpense Tracker")
    print("1. Add an expense")
    print("2. Show total")
    print("3. Quit")

    choice = input("Choose an option: ")

    if choice == "1":
        print("Add expense coming soon...")
    elif choice == "2":
        print("Show total coming soon...")
    elif choice == "3":
        print("Goodbye!")
        break
    else:
        print("Invalid choice. Try again.")

Run it:

python expense_tracker.py

Try each option. Type 3 to quit. Type 9 to see what happens with an invalid choice.

Expense Tracker
1. Add an expense
2. Show total
3. Quit
Choose an option: 1
Add expense coming soon...

Expense Tracker
1. Add an expense
2. Show total
3. Quit
Choose an option: 3
Goodbye!

Notice what's happening here. The while True loop keeps the program running after each action. The if/elif chain routes each choice to its action. The break statement is the only way out—and it only triggers when the user picks option 3.

The \n at the start of the first print adds a blank line before the menu, which keeps the output from feeling cramped after previous actions.

Common mistake: the infinite loop

If you forget the break statement, or put it in the wrong place, the program will never exit. You'll be stuck in the menu forever, even after choosing "Quit."

If that happens, press Ctrl+C in your terminal to force the program to stop. Then check that your break is inside the elif choice == "3" block, not somewhere else.

Knowledge check

Check your understanding

Answer this question before you continue.

The menu keeps appearing after the user enters `3`. Which fix matches the article’s program?
Debugging

Focus: Diagnose a menu loop that does not exit when the user chooses the quit option.

```python
elif choice == "3":
    print("Goodbye!")
    # missing statement
```

Add an Expense with a Function

The menu works, but it doesn't do anything useful yet. Time to make option 1 actually add an expense.

We'll write a function that asks the user for an amount and a category, builds a dictionary, and appends it to the expenses list.

First, add an empty list near the top of your file, before the while loop:

expenses = []

Now define the function. In Python, function definitions must come before the code that calls them, so place this above the while loop too:

def add_expense(expenses):
    amount = float(input("Amount: "))
    category = input("Category: ")
    expenses.append({"amount": amount, "category": category})
    print("Expense added.")

Then update the menu loop to call it:

while True:
    print("\nExpense Tracker")
    print("1. Add an expense")
    print("2. Show total")
    print("3. Quit")

    choice = input("Choose an option: ")

    if choice == "1":
        add_expense(expenses)
    elif choice == "2":
        print("Show total coming soon...")
    elif choice == "3":
        print("Goodbye!")
        break
    else:
        print("Invalid choice. Try again.")

Run it and add an expense:

python expense_tracker.py
Expense Tracker
1. Add an expense
2. Show total
3. Quit
Choose an option: 1
Amount: 24.50
Category: groceries
Expense added.

Here's what just happened, step by step:

  1. input("Amount: ") returned the string "24.50".
  2. float() converted that string to the number 24.5.
  3. The dictionary {"amount": 24.5, "category": "groceries"} was created.
  4. .append() added it to the expenses list.

Common mistake: forgetting to convert the string

This is the most common beginner error in this project. input() always returns a string. If you skip the float() conversion, your amount stays text:

amount = input("Amount: ")  # amount is "24.50", not 24.5

Later, when you try to add amounts together, Python will either concatenate strings or throw an error. The float() conversion is what turns user text into a number you can do math with.

Why float and not int? Because expenses have cents. int only handles whole numbers. float handles decimals.

Knowledge check

Check your understanding

Answer this question before you continue.

Why does `add_expense` use `float(input("Amount: "))` instead of storing `input("Amount: ")` directly?
Misconception Check

Focus: Explain why an amount read with input must be converted to a numeric type before calculation.

Calculate and Show the Total

Now let's make option 2 show something useful. We'll write a function that loops through all stored expenses and adds up the amounts.

def show_total(expenses):
    total = 0
    for expense in expenses:
        total += expense["amount"]
    print(f"Total: ${total:.2f}")

Update the menu to call it:

    if choice == "1":
        add_expense(expenses)
    elif choice == "2":
        show_total(expenses)

Run the program, add two expenses, then check the total:

Expense Tracker
1. Add an expense
2. Show total
3. Quit
Choose an option: 1
Amount: 24.50
Category: groceries
Expense added.

Expense Tracker
1. Add an expense
2. Show total
3. Quit
Choose an option: 1
Amount: 12.00
Category: transport
Expense added.

Expense Tracker
1. Add an expense
2. Show total
3. Quit
Choose an option: 2
Total: $36.50

The for loop visits each dictionary in the list. Each iteration pulls out the value stored under the "amount" key and adds it to total.

The :.2f inside the f-string formats the number with exactly two decimal places. Without it, you'd see $36.5 instead of $36.50. With it, the output reads like money.

Why compute the total from stored data?

You might wonder: why not just keep a running total variable and update it every time an expense is added?

Because that approach breaks the moment you load data from a file. If you have 50 saved expenses and you add one more, a running total would only know about the new one. Computing the total from the list every time guarantees it's always correct, no matter how the list was built.

This is a recurring idea in programming: store the source data, and derive the answers you need from it. Derived values can go stale. Source data stays reliable.

Save Expenses So They Survive a Restart

Right now, your tracker has a serious flaw: quit the program and every expense vanishes. The list lives only in memory, and memory is wiped clean when the process ends.

To fix this, we need two things: save the list to a file, and load it back when the program starts.

Saving

Python's json module converts lists and dictionaries into a text format you can write to a file. Add this function:

import json

def save_expenses(expenses):
    with open("expenses.json", "w") as file:
        json.dump(expenses, file)

The import json line goes at the very top of your file.

Then call save_expenses(expenses) right after an expense is added. This way, each successful entry is written to disk immediately. If the program crashes or you close the terminal window, you lose at most the entry you were typing—not everything since the last clean quit.

Update the add_expense function to save after appending:

def add_expense(expenses):
    amount = float(input("Amount: "))
    category = input("Category: ")
    expenses.append({"amount": amount, "category": category})
    save_expenses(expenses)
    print("Expense added.")

Note: Saving after every addition is a deliberate reliability choice. An alternative is saving only when the user chooses "Quit." That version is simpler to reason about, but it means a crash or a closed terminal loses every expense from that session. For a tool whose whole point is remembering your data, saving immediately is worth the extra line.

Loading

When the program starts, it should check whether a saved file exists and load it. Add this function:

def load_expenses():
    try:
        with open("expenses.json", "r") as file:
            return json.load(file)
    except FileNotFoundError:
        return []

Then replace your empty list at the top of the file:

expenses = load_expenses()

Why the try/except?

On the very first run, expenses.json doesn't exist yet. If you try to open a file that isn't there, Python raises a FileNotFoundError. The try/except catches that error and returns an empty list instead of crashing.

This is a normal pattern in file handling: attempt the operation, and handle the case where the file is missing.

Knowledge check

Check your understanding

Answer this question before you continue.

What should `load_expenses()` do when `expenses.json` does not exist on the first run?
Debugging

Focus: Choose the error-handling behavior that lets the tracker start when no save file exists.

The full picture

Flowchart of the expense tracker: the program starts by loading expenses or an empty list, displays a menu, then branches to add an expense and save the list, calculate and show the total, or quit; after adding or showing the total, it returns to the menu.
The complete control flow connects input, list storage, JSON persistence, total calculation, and the menu loop.

Here's your complete program. This is the authoritative version—if you've been editing piece by piece, replace your whole file with this:

import json

def load_expenses():
    try:
        with open("expenses.json", "r") as file:
            return json.load(file)
    except FileNotFoundError:
        return []

def save_expenses(expenses):
    with open("expenses.json", "w") as file:
        json.dump(expenses, file)

def add_expense(expenses):
    amount = float(input("Amount: "))
    category = input("Category: ")
    expenses.append({"amount": amount, "category": category})
    save_expenses(expenses)
    print("Expense added.")

def show_total(expenses):
    total = 0
    for expense in expenses:
        total += expense["amount"]
    print(f"Total: ${total:.2f}")

expenses = load_expenses()

while True:
    print("\nExpense Tracker")
    print("1. Add an expense")
    print("2. Show total")
    print("3. Quit")

    choice = input("Choose an option: ")

    if choice == "1":
        add_expense(expenses)
    elif choice == "2":
        show_total(expenses)
    elif choice == "3":
        print("Goodbye!")
        break
    else:
        print("Invalid choice. Try again.")

Now run the program twice. First run: add an expense and quit.

Expense Tracker
1. Add an expense
2. Show total
3. Quit
Choose an option: 1
Amount: 24.50
Category: groceries
Expense added.

Expense Tracker
1. Add an expense
2. Show total
3. Quit
Choose an option: 3
Goodbye!

Second run: check the total without adding anything.

Expense Tracker
1. Add an expense
2. Show total
3. Quit
Choose an option: 2
Total: $24.50

Your expense survived the restart. That's persistence—the feature that turns a demo into a tool.

You can also open expenses.json in your editor and see exactly what was saved:

[{"amount": 24.5, "category": "groceries"}]

Common mistake: overwriting instead of appending

When you open a file in "w" mode, Python wipes the file and writes fresh content. That's correct here because you're writing the entire list every time.

The mistake beginners make is trying to append one expense to the file instead of rewriting the whole list. That approach leads to corrupted JSON and confusing errors. The cleaner mental model: your list is the source of truth, and the file is just a snapshot of that list. Save the whole snapshot each time.

Common Beginner Mistakes and How to Fix Them

Let's walk through the errors most likely to trip you up while building this project.

String-to-number conversion error

Symptom: You enter an amount and the program crashes with ValueError: could not convert string to float.

What's happening: You typed something that can't become a number, like abc or $24.50. The $ sign breaks the conversion.

The fix: Make sure you enter plain numbers like 24.50. If you want the program to handle bad input gracefully, you'd need a loop that keeps asking until the user enters a valid number—a good next upgrade once the basic version works.

File-not-found error on first run

Symptom: The program crashes with FileNotFoundError before you even see the menu.

What's happening: Your load_expenses() function is missing the try/except, or you're trying to open the file before it exists.

The fix: Use the try/except FileNotFoundError pattern shown above. The first run has no file, and that's expected, not an error.

The overwrite trap

Symptom: You add expenses, quit, restart, and only the last session's expenses are there. Earlier ones vanished.

What's happening: You're saving only new expenses instead of the full list, or you're opening the file in append mode and creating invalid JSON.

The fix: Always save the entire expenses list with json.dump(expenses, file). The list holds everything from both the current session and the loaded file.

The infinite loop

Symptom: Choosing "Quit" doesn't quit. The menu just reappears.

What's happening: The break statement isn't being reached. Maybe it's outside the while loop, or the condition checking the choice is wrong.

The fix: Press Ctrl+C to escape, then check that break is inside the elif choice == "3" block. Also verify you're comparing choice to the string "3", not the number 3.

Test Your Tracker Like a Builder

You've built the program. Now verify it actually works—not by hoping, but by running a deliberate test sequence.

First, make sure you're starting from a clean state. If you've been following along, expenses.json may already contain test data. Delete it (or use a fresh project folder) so your expected totals aren't thrown off by old entries:

rm expenses.json

Now run the test sequence:

  1. Run the program.
  2. Add an expense with amount 10.00 and category test.
  3. Add another with amount 5.50 and category test2.
  4. Show the total. It should read $15.50.
  5. Quit.
  6. Run the program again.
  7. Show the total immediately. It should still read $15.50.

If step 7 shows $15.50, every feature is working: input, storage, calculation, saving, and loading.

If something fails, don't panic. Read the error message. It tells you exactly which line broke and what Python expected. Fix that one thing and run again.

Testing your own program is not an optional extra. It's the habit that separates people who write code from people who ship working tools. Every time you run the program and check the output, you're learning something about how your code actually behaves—not how you imagine it behaves.

Once the test passes, make one small change and observe the consequence. Change the menu text. Add a blank line somewhere. Break something on purpose and see what error you get. This play-and-observe loop is how you build an instinct for what your code is doing.

Next Steps to Make It Yours

Your tracker works. Now make it yours.

Here are three beginner-safe extensions, in rough order of difficulty:

Handle bad input gracefully. Right now, typing abc as an amount crashes the program. Add a loop that keeps asking until the user enters a valid number. This practices while loops and try/except together.

Delete an expense. Add a menu option that shows all expenses with numbers, asks which one to remove, and deletes it from the list. This exercises list indexing and the del statement.

Filter by category. Add an option that asks for a category and shows only expenses in that category, plus their subtotal. This practices if statements inside a for loop.

Any of these will push your understanding further than reading another tutorial will. Pick one and build it.

What you've just done matters more than the specific program. You took separate skills—input, functions, dictionaries, loops, conditionals, file I/O—and combined them into one system that solves a real problem. That's the core of programming: not memorizing syntax, but assembling tools into something useful.

Your tracker is proof you can do it. The next project will be easier.

Knowledge check

Final check

Finish the article by checking the ideas you just learned.

What does `show_total` print for these stored expenses?
Question 1 of 2Output Prediction

Focus: Predict the formatted total produced from stored expense amounts.

```python
expenses = [
    {"amount": 10.00, "category": "test"},
    {"amount": 5.50, "category": "test2"},
]
show_total(expenses)
```
After a restart, why can the tracker still show an expense entered during an earlier run?
Question 2 of 2Single Choice

Focus: Explain how saving and loading the complete expense list preserves data across program restarts.

8sources checked
8source domains
5searches run

Research updated Sep 5, 2026

Practical resource

Want a more structured Python path?

Use the Python Starter Pack to turn scattered tutorials into a focused practice path.

View the bundle
Coming soon

Python Starter Pack

A compact LearnPyFast PDF pack covering what Python is, installation, your first program, running Python code, and Python versions.

$9
PDF BundleTopic PackPythonBeginner
  • 5 curated chapters
  • Enhanced PDF edition with bundle-only learning guidance
  • Offline-friendly format for focused review
  • Source article links for future online updates

Coming soon

Free Python bundle

Get the LearnPyFast Python Starter Bundle

A focused collection of beginner-friendly Python resources to help you move from setup to building practical projects.

You’ll receive the bundle by email. You can unsubscribe anytime.

No spam. You can unsubscribe anytime. See our Privacy policy.

Related sites

Continue beyond Python

Explore related Worldmonger sites when you want to move from Python basics into JavaScript or LLM application building.

JavaScript tutorialstutorial

LearnJSFast

Beginner-friendly JavaScript tutorials for practical web development and self-taught developers.

JavaScriptFrontendWeb development
Visit LearnJSFast
LLM tutorialstutorial

LearnLLMFast

Practical LLM tutorials for builders who want to understand prompting, workflows, agents, and AI applications.

LLMAIBuilders
Visit LearnLLMFast

Keep learning

Related tutorials

Continue with nearby Python topics and beginner-friendly explanations.

Captivating view of a stormy sea under dark clouds, showcasing powerful ocean waves.
beginner
6 min read

Beginner Python Project Ideas

You finished the syntax tutorials. You know what a loop does, you can write a function, and you understand what a dictionary is for. Then you close the…

Read tutorial