PythonMastery
beginner 20 min read · lesson 1 of 15 in Projects

Project: Tip Calculator

1 · The lesson

read

You're going to build a small, working program that asks for a bill amount, asks how many people are splitting, and prints exactly what each person owes, including the tip. It's the kind of program that people who don't program would really use.

What you'll practise: input(), type conversion, arithmetic, f-strings, exception handling, functions.

What you'll have at the end: a Python script you can run on your phone or laptop to split the next restaurant bill.


Step 1: The Simplest Possible Version

Start with hardcoded values. Get the math right first.

python
bill = 87.50
tip_percent = 18
num_people = 3

tip = bill * tip_percent / 100
total = bill + tip
per_person = total / num_people

print(f"Tip ({tip_percent}%):   ${tip:.2f}")
print(f"Total:           ${total:.2f}")
print(f"Per person:      ${per_person:.2f}")

Run it. Change the values and run again. Make sure the numbers look right.


Step 2: Connect input()

Now make it interactive. Remember: input() returns a string, so we need to convert it to a number.

python
# In a real terminal, you'd use input(). In the browser, we simulate it:
bill_text = "87.50"        # input("How much was the bill? $")
tip_text = "18"            # input("Tip percent? ")
people_text = "3"          # input("How many people? ")

bill = float(bill_text)
tip_percent = float(tip_text)
num_people = int(people_text)

tip = bill * tip_percent / 100
total = bill + tip
per_person = total / num_people

print(f"Tip ({tip_percent:.0f}%):   ${tip:.2f}")
print(f"Total:           ${total:.2f}")
print(f"Per person:      ${per_person:.2f}")

In a real script, use these lines instead (remove the # at the start):

python
# bill = float(input("How much was the bill? $"))
# tip_percent = float(input("Tip percent? "))
# num_people = int(input("How many people? "))

Step 3: Handle Bad Input Gracefully

Steps 3 and 4 and the final exercise use def, if and raise, and Step 4 also uses a for loop and a dictionary. Later lessons teach these: Conditionals, Loops, Functions, Dictionaries and Exceptions. If you have not done those lessons yet, run the code below and read it, then come back to it later.

What if someone types "abc"? The simple version crashes with a ValueError. And if someone types "-50", it runs but prints negative amounts, which make no sense. A better version explains what is wrong.

python
def get_number(prompt, source_text, allow_int_only=False, allow_zero=False):
    """Convert a string to a number, raising a friendly error if it is not valid."""
    try:
        value = int(source_text) if allow_int_only else float(source_text)
    except ValueError:
        raise ValueError(f"{prompt}: '{source_text}' isn't a valid number")
    if value < 0:
        raise ValueError(f"{prompt}: can't be negative, got {value}")
    if value == 0 and not allow_zero:
        raise ValueError(f"{prompt}: must be more than zero")
    return value

try:
    bill = get_number("Bill amount", "87.50")
    tip_percent = get_number("Tip percent", "18", allow_zero=True)  # 0 means no tip
    num_people = get_number("Number of people", "3", allow_int_only=True)

    tip = bill * tip_percent / 100
    total = bill + tip
    per_person = total / num_people

    print(f"Tip ({tip_percent:.0f}%):   ${tip:.2f}")
    print(f"Total:           ${total:.2f}")
    print(f"Per person:      ${per_person:.2f}")
except ValueError as e:
    print(f"⚠️  {e}")

The pattern: check the inputs in a helper function, raise ValueErrors with clear messages, and catch them all in one place. Notice that the math hasn't changed. It now runs only after every input has been checked.

Try changing "87.50" to "abc" or "3" to "0" and click Run again. You'll see the friendly error message instead of a traceback.


Step 4: Make It a Reusable Function

Wrap the calculation so other code can call it.

python
def calculate_split(bill, tip_percent, num_people):
    """Return a dict with tip, total, and per-person amounts."""
    if bill <= 0 or tip_percent < 0 or num_people <= 0:
        raise ValueError("All inputs must be positive (tip can be zero)")
    tip = bill * tip_percent / 100
    total = bill + tip
    return {
        "tip": tip,
        "total": total,
        "per_person": total / num_people,
    }

# Try it with a few scenarios
scenarios = [
    (87.50, 18, 3),
    (45.00, 20, 2),
    (120.00, 15, 4),
]

for bill, tip_pct, people in scenarios:
    result = calculate_split(bill, tip_pct, people)
    print(f"${bill:>7.2f} @ {tip_pct}% for {people} people → ${result['per_person']:.2f} each")

Now the same function works for a single bill and for a comparison table, without repeating any math.


Stretch Goals: Make It Yours

Pick any of these to extend the project:

1. Round up per-person amount to the next 50¢, so the amounts are easier to pay in real life. Hint: math.ceil(amount * 2) / 2.
2. Add a "service quality" prompt that maps "great"/"good"/"ok" to 20%/18%/15% automatically.
3. Subtotal by item: let the user enter several item prices and sum them.
4. Handle several bills: wrap it in a while True: loop that lets the user calculate multiple bills until they type quit.
5. Save the history: append each calculation to a bills.txt log with a timestamp (you'll use the File I/O lesson for this).


🎯 Your Turn: Build It Without Looking

Now write your own version from scratch. The skeleton below has TODOs. Try to fill them in before opening the hints.

Goal: a function split_bill(bill, tip_percent, num_people) that returns the per-person amount including tip. It checks that the bill and the number of people are positive, and that the tip percent is not negative.

This exercise uses def, if and raise, which you will learn in the Functions, Conditionals and Exceptions lessons. For now, copy the skeleton as it is and fill in the marked lines.

python
def split_bill(bill, tip_percent, num_people):
    # TODO 1: raise ValueError if bill or num_people is not positive, or tip_percent is negative
    # TODO 2: calculate the tip
    # TODO 3: return (bill + tip) / num_people
    pass

# In a real terminal, get values from input() and pass them in.
# For testing here, hardcode some numbers:
result = split_bill(87.50, 18, 3)
print(f"Per person: ${result:.2f}")  # expect about $34.42
Hint 1: Validation pattern Use a single if with or to check all three at once: if bill <= 0 or tip_percent < 0 or num_people <= 0: raise ValueError("...") Tip percent can be zero (no tip), but bill and num_people must be positive.
Hint 2: The math tip = bill * tip_percent / 100, then return (bill + tip) / num_people. That's it.
Show full solution
python
def split_bill(bill, tip_percent, num_people):
    if bill <= 0 or tip_percent < 0 or num_people <= 0:
        raise ValueError("bill and num_people must be positive; tip can be zero")
    tip = bill * tip_percent / 100
    return (bill + tip) / num_people

# With real input():
# bill = float(input("Bill amount: "))
# tip_percent = float(input("Tip %: "))
# num_people = int(input("Number of people: "))
# print(f"Each pays: ${split_bill(bill, tip_percent, num_people):.2f}")

print(f"Per person: ${split_bill(87.50, 18, 3):.2f}")

What You Built

Look at the final code. You now have:


  • Input validation that doesn't crash

  • A reusable function with a clear contract

  • Money formatting with :.2f

  • An exception-handling pattern you will use again in many programs

This is what a small, real Python program looks like. It is more than an x = 5 exercise: it is a short script that does one useful job.

Next project: Number Guessing Game: adds loops and random numbers.

Practice this

on practicepython.in

Short exercises that run in your browser and tell you what your code did, as well as whether a test passed.

Found this useful? Share it with a friend who’s learning Python.