Project: Tip Calculator
1 · The lesson
readYou'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.
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.
# 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):
# 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.
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.
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.
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 singleif 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
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.inShort 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.