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 — tip included. It's the kind of program a non-programmer would actually use.
What you'll practice: 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 settle 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. Tweak the values and run again. Make sure the numbers look right.
Step 2 — Wire Up input()
Now make it interactive. Remember: input() returns a string, so we need to convert.
# 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, just swap the comments back in:
# 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
What if someone types "abc" or "-50"? The naive version crashes. The real version explains.
def get_positive_number(prompt, source_text, allow_int_only=False): """Convert a string to a positive number, raising a friendly error otherwise.""" 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}: must be positive, got {value}") return value try: bill = get_positive_number("Bill amount", "87.50") tip_percent = get_positive_number("Tip percent", "18") num_people = get_positive_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: validate inputs in a helper function, raise descriptive ValueErrors, catch them all in one place. Notice how the math hasn't changed — it's just wrapped in trust.
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 logic powers a single-bill calculator AND a comparison table — without duplicating 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 math is friendlier 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. Track the running tally — 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 Peeking
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. Validates that all inputs are positive.
def split_bill(bill, tip_percent, num_people): # TODO 1: validate all three are positive — raise ValueError if not # 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 ≈ $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'll reuse forever
This is what a small Python program actually looks like in real code. Not toy x = 5 exercises — a tiny script that does one useful thing well.
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 actually did, not just whether a test passed.