Project: Command-Line Calculator

Put your skills together to build a fully working command-line calculator with a menu, multiple operations, and history.

~21 min Beginner

What you will build, and the plan

In this lesson you will build a real project from scratch. The calculator runs in the terminal, shows a menu, lets the user choose an operation, performs the calculation, displays the result, and keeps a history of past calculations. By the end you will have a complete Python program that uses almost everything from this course.

Before writing any code, plan. Think about what the program needs to do:

  • Show a menu: Add, Subtract, Multiply, Divide, View History, Quit
  • Ask the user to choose an option
  • Ask for two numbers
  • Perform the correct calculation
  • Handle errors like dividing by zero or bad input
  • Save the calculation to a history list
  • Keep running until the user chooses to quit

Why plan first? A list like this turns one big scary task into seven small ones. Each line becomes a piece you can build and test on its own. Professional programmers do the same thing, often on paper before they open the editor. The pieces will map to these tools:

  • Functions for each calculation and for each screen
  • A dictionary to connect the menu number to the right function
  • A list to store the history
  • A while loop to keep the program running
  • try and except to stop bad input from crashing it

Step 2: the calculation functions

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

def divide(a, b):
    if b == 0:
        return None   # signal that division by zero happened
    return a / b

Each function does one small job and returns the answer. None of them print anything or ask the user for anything. That keeps them simple, and easy to test. You could even run print(add(2, 3)) by itself to check it works.

divide is the only tricky one. Dividing by zero would crash the program, so we check first. If b is 0, we return None, which means "no result". The main program will see None and show an error message instead. This is a common design: the function reports a problem by what it returns, and the caller decides how to react.

Step 3: the helper functions

def get_number(prompt):
    while True:
        try:
            return float(input(prompt))
        except ValueError:
            print("Please enter a valid number.")

def show_menu():
    print()
    print("--- Calculator ---")
    print("1. Add")
    print("2. Subtract")
    print("3. Multiply")
    print("4. Divide")
    print("5. View history")
    print("6. Quit")
    print("-" * 18)

def show_history(history):
    if not history:
        print("No calculations yet.")
    else:
        print()
        print("Calculation History:")
        for i, entry in enumerate(history, 1):
            print(f"  {i}. {entry}")
  • get_number keeps asking until the user types a real number. float() means decimals are welcome. A bad answer triggers ValueError, and the loop simply asks again.
  • show_menu only prints. "-" * 18 repeats a dash 18 times to draw a line.
  • show_history receives the history list as a parameter. An empty list counts as false, so if not history catches the empty case. enumerate(history, 1) numbers the entries from 1.

Splitting the work like this means the main loop later reads almost like English.

Step 4: the main program loop

def main():
    history = []
    operations = {
        "1": (add, "+"),
        "2": (subtract, "-"),
        "3": (multiply, "*"),
        "4": (divide, "/"),
    }

    print("Welcome to the Python Calculator!")

    while True:
        show_menu()
        choice = input("Choose an option (1-6): ").strip()

        if choice == "6":
            print("Goodbye!")
            break

        elif choice == "5":
            show_history(history)

        elif choice in operations:
            func, symbol = operations[choice]
            a = get_number("Enter first number: ")
            b = get_number("Enter second number: ")
            result = func(a, b)

            if result is None:
                print("Error: Cannot divide by zero.")
            else:
                print(f"Result: {a} {symbol} {b} = {result}")
                entry = f"{a} {symbol} {b} = {result}"
                history.append(entry)
        else:
            print("Invalid choice. Please enter a number from 1 to 6.")

main()

This is the heart of the program. Let us walk through the important ideas:

  • The operations dictionary maps each menu choice to a pair: the function and its symbol. This avoids a long chain of if and elif for the four calculations.
  • func, symbol = operations[choice] unpacks the pair. Now func is one of our functions, and func(a, b) calls it. Functions are values, so you can store them in a dictionary.
  • .strip() removes accidental spaces from the choice.
  • result is None detects the divide by zero case. We use is None because None is a special value.
  • while True with break keeps showing the menu until the user picks 6.
  • history.append(entry) saves every successful calculation, ready for option 5.

Everything runs inside main(), and the last line main() starts it. Putting your program in a main function is a common habit.

Step 5: run it, test it, extend it

Save everything in one file called calculator.py. Put the functions first and main() at the end. Open your terminal, go to the folder where you saved it, and run:

python calculator.py

Now test it properly. Do not just try the happy path. Try each of these:

  • Add, subtract, multiply and divide normal numbers
  • Divide by zero
  • Type a word instead of a number
  • View the history before any calculation, and after a few
  • Type an invalid menu choice like 9 or abc
  • Choose 6 to quit

A sample session might look like this:

--- Calculator ---
1. Add
2. Subtract
3. Multiply
4. Divide
5. View history
6. Quit
------------------
Choose an option (1-6): 1
Enter first number: 8
Enter second number: 2.5
Result: 8.0 + 2.5 = 10.5

If something goes wrong, read the error message and the line number, add a print() to see the values, and fix one thing at a time. Debugging your own program is part of the project, not a sign that you failed.

Once it works, make it yours. Here are ideas to extend it:

  • Add more operations: square root, exponent, percentage
  • Save history to a file so it persists between sessions
  • Let the user clear the history
  • Format numbers more cleanly (show 8 instead of 8.0)
  • Let the user use the last result as the first number in the next calculation
What you practiced: this project used functions, dictionaries, lists, loops, if/else, error handling, user input and f-strings. Every concept from the course came together in one working program. That is what programming is. Small pieces, combined thoughtfully, to build something useful.

A small upgrade: cleaner numbers

One thing you may have noticed is that the results show 8.0 instead of 8. This happens because float() always gives decimals. Here is a helper that shows whole numbers without the .0:

def clean(number):
    if number == int(number):
        return int(number)
    return number

print(clean(8.0))    # 8
print(clean(10.5))   # 10.5

Now use it in the main loop when you print the result and build the history entry:

print(f"Result: {clean(a)} {symbol} {clean(b)} = {clean(result)}")

The small helper changed the feel of the whole program, and you only had to add one function and change one line. That is the benefit of splitting a program into pieces.

A second upgrade: saving history to a file

Right now the history is lost when the program ends. You learned about files, so let us save it. Add two functions:

HISTORY_FILE = "history.txt"

def load_history():
    try:
        with open(HISTORY_FILE, "r") as file:
            return [line.strip() for line in file]
    except FileNotFoundError:
        return []

def save_entry(entry):
    with open(HISTORY_FILE, "a") as file:
        file.write(entry + "\n")

Then in main(), start with history = load_history() instead of an empty list, and call save_entry(entry) right after history.append(entry). Run the calculator, quit, and run it again. Your old calculations are still there.

Notice how many lessons that touched: files, try and except, list comprehensions, functions and constants. Real projects always mix skills like this.

Thinking like a programmer

The calculator is small, but the way you built it is how every program is built:

  1. Understand the goal. What should it do, in plain words?
  2. Break it into small steps. Each step becomes a function or a loop.
  3. Build one piece at a time. Test each piece before adding the next.
  4. Handle the unexpected. Bad input, empty data, zero.
  5. Improve it. Add features, clean up, and make it easier to read.

You might also try these challenges on your own, all using only what you learned in this course:

  • A number guessing game with a limited number of tries
  • A to-do list manager that saves tasks to a file
  • A quiz program that stores questions in a dictionary
  • A simple contact book that uses a class for each contact

Do not copy solutions. Struggle a bit first. The struggle is where the learning happens.

Key takeaways

  • Plan first. Write down what the program must do before you write code
  • Small functions that return values are easy to build and test
  • A dictionary can connect menu choices to functions
  • A while True loop with break is the classic menu loop
  • try and except plus a return value like None handle bad input and divide by zero
  • A list stores history, and a file can make it permanent
  • Test the unhappy paths too: zero, words and wrong choices
Sign up to track your progress Mark lessons as read and pick up where you left off Sign up free to unlock this practice Your first practice lesson is free to try. Create an account to unlock the rest and save your progress