Learning Goals
3 minBy the end of this lesson you can:
- Use
returnto hand a value back from a function, and capture it into a variable at the call site. - Explain the difference between
print(shows on screen) andreturn(gives back to the caller). - Compose functions — pass the return value of one straight into another like
print(square(5)).
Warm-Up
5 minQuick prediction. Two functions, very similar. What does each line print?
def square_print(n): print(n * n) def square_return(n): return n * n a = square_print(5) b = square_return(5) print("a is", a) print("b is", b)
Show the answers
25 a is None b is 25
square_print(5) shows 25 on screen immediately, then gives back nothing — so a ends up holding None (Python's way of saying "empty"). square_return(5) shows nothing on screen by itself, but hands back 25 for b to grab. That gap — between showing a value and handing it back — is everything today is about.
print talks to the human. return talks to the rest of your program. They're completely different jobs.
New Concept · return Is the Reply Channel
14 minThe shape
def square(n): return n * n
That's it. return followed by any value (a number, a string, a list, the result of an expression). Whatever follows return is the value the function hands back when it's called.
Catch the value at the call site
The call now evaluates to that value — it's no longer just an action, it's an expression. You can store it in a variable, do maths with it, print it, anything:
result = square(7) # store it print(result) # → 49 total = square(3) + square(4) # combine returns print(total) # → 9 + 16 = 25
Compose: feed one return into another
Because the call evaluates to a value, you can pass it straight into another call. This is where functions become a real building set:
print(square(5)) # → 25 print(square(square(3))) # square(3)=9, then square(9)=81
return exits the function immediately
The moment Python hits return, the function stops. Anything after it never runs:
def is_positive(n): if n > 0: return True return False print("you'll never see this") # unreachable
This makes return useful for early exits — handle a special case, return, done.
A function without return hands back None
If you don't write a return, Python silently returns the special value None for you. That's why a = square_print(5) in the warm-up left a as None. None is Python's way of saying "there's no value here."
print vs return · the table
# print(value) # - shows the value on the screen, for a human # - the rest of the program can't use what was printed # # return value # - hands the value back to whoever called the function # - nothing appears on the screen unless someone prints it # - the rest of the program CAN store it, combine it, pass it on
Functions that compute something should return the answer. Functions whose job is to show something to the user should print. Mixing the two in one function is usually a code smell — one job per function.
Worked Example · The Total Calculator
13 minThe job
A canteen sells nasi lemak at RM 4.50 a plate. You need a function that tells you the total for any number of plates, plus a 6% service charge. You'll then build a tiny "table of totals" that prints the cost for 1, 2, 3, 4, and 5 plates.
Step 1 · Write the function
def total_for(plates): subtotal = plates * 4.50 grand = subtotal * 1.06 # 6% service return grand
No print anywhere in the function. Just compute and return.
Step 2 · Use it in a loop
for n in range(1, 6): print(n, "plate(s) →", total_for(n))
1 plate(s) → 4.77 2 plate(s) → 9.54 3 plate(s) → 14.31 4 plate(s) → 19.080000000000002 5 plate(s) → 23.85
Ignore the long decimal — that's just how floats work, not your bug. The point: one tiny function, five different totals, all from one call inside a loop.
Step 3 · Compose two functions
Round the total to 2 decimal places with the built-in round() — composed straight onto our return value:
for n in range(1, 6): print(n, "plate(s) → RM", round(total_for(n), 2))
1 plate(s) → RM 4.77 2 plate(s) → RM 9.54 3 plate(s) → RM 14.31 4 plate(s) → RM 19.08 5 plate(s) → RM 23.85
Step 4 · The whole file
Save as nasi_lemak_total.py:
Code
# nasi_lemak_total.py — return values in action def total_for(plates, price=4.50, service=0.06): subtotal = plates * price grand = subtotal * (1 + service) return grand # main program — a tiny totals table print("Plates | Total") print("-" * 18) for n in range(1, 6): rm = round(total_for(n), 2) print(" ", n, " | RM", rm) # and one custom case — bulk order, special price bulk = round(total_for(20, price=4.00), 2) print() print("Bulk order (20 plates @ RM 4.00):", "RM", bulk)
Sample run
Plates | Total ------------------ 1 | RM 4.77 2 | RM 9.54 3 | RM 14.31 4 | RM 19.08 5 | RM 23.85 Bulk order (20 plates @ RM 4.00): RM 84.8
Every real program is built like this — small functions that compute and return, then a tiny main program that wires the returns together. The Python standard library, every web framework, every game engine. Today you learnt the move that the next million lines of code will all be made of.
Try It Yourself
14 minThree tasks. Every function should return its answer — no print inside the function.
Write double(n) that returns n * 2. Call it on 5, store the result in a variable, then print the variable.
Hint
def double(n): return n * 2 result = double(5) print(result) # → 10
If you wrote print(n * 2) inside the function instead of return n * 2, you'd see 10 on screen but result would be None. Try it both ways to feel the difference.
Write c_to_f(c) that returns the Fahrenheit value of a Celsius temperature. The formula is c * 9 / 5 + 32. Use the function inside a loop to print a conversion table for 0, 10, 20, 30, 40 Celsius.
Hint
def c_to_f(c): return c * 9 / 5 + 32 for c in [0, 10, 20, 30, 40]: print(c, "C →", c_to_f(c), "F")
Expected output: 0 C → 32.0 F, 10 C → 50.0 F, and so on up to 40 C → 104.0 F. The function does the maths, the loop handles the display.
Write add(a, b) that returns a + b. Write square(n) that returns n * n. Without any extra variables, print the value of square(add(3, 4)) — the squared result of three plus four. Predict what it should print before you run it.
Hint
def add(a, b): return a + b def square(n): return n * n print(square(add(3, 4))) # → 49
Python evaluates the innermost call first: add(3, 4) returns 7. Then square(7) returns 49. Then print shows it. Three nested calls, one line — and only possible because both functions return.
Mini-Challenge · Ben's None Mystery
8 minBen's script keeps printing None in weird places. He thinks Python is broken. Read his code and find the two bugs.
# ben_areas.py — buggy
def area_of_square(side):
print(side * side)
def area_of_rectangle(width, height):
return
width * height
a = area_of_square(4)
b = area_of_rectangle(3, 5)
print("Square area:", a)
print("Rectangle area:", b)Run it. He sees 16 appear on its own, then both a and b come out as None. Find the two bugs:
- Bug 1.
area_of_squareusesprintwhere it should usereturn. The screen sees the answer, butaonly ever getsNone. - Bug 2.
area_of_rectanglehasreturnon its own line, with the actual expression on the next line.returnwith nothing after it returnsNone— thewidth * heightline is then dead code.
Show one possible fix
# ben_areas.py — fixed def area_of_square(side): return side * side def area_of_rectangle(width, height): return width * height a = area_of_square(4) b = area_of_rectangle(3, 5) print("Square area:", a) # → 16 print("Rectangle area:", b) # → 15
Two of the most common return bugs in real life. Bug 1 — printing instead of returning — is the textbook beginner mistake. Bug 2 — return on its own line followed by the expression — happens because Python doesn't treat that as one statement; it's return (which means "return None") and then a dead expression on the next line.
Recap
3 minreturn is how a function sends a value back to whoever called it. The call site can then capture the value into a variable, combine it with other values, or pass it straight into another function call. The moment return runs, the function exits — so anything after it doesn't happen. A function with no return silently returns None. Functions that compute answers should return; functions whose only job is to show things to a human can print. Next lesson we'll practise building a math toolbox where every function returns its answer cleanly.
Vocabulary Card
return- The keyword that hands a value back from a function. The function stops as soon as
returnruns. - return value
- The value that follows
return. Becomes the value of the function call at the call site. None- Python's special "nothing" value. Any function that doesn't use
returnhands backNone. - composing functions
- Passing the return value of one function straight into another, like
print(square(5)). Only possible because functions return. printvsreturnprinttalks to the human.returntalks to the rest of the program. Different jobs, different keywords.
Homework
4 minCreate a new file tip_calculator.py — a small tip-and-split helper for eating out with friends.
- Define
tip(bill, percent=10)that returns the tip amount —bill * percent / 100. No print inside the function. - Define
split(total, people)that returns the per-person share —total / people. - In the main program, set
bill = 84.00andpeople = 4. - Call
tip(bill)(default 10%) and store the result int. - Print the tip and the grand total (bill + t).
- Call
split(bill + t, people)and store inshare. Print the per-person share, rounded to 2 decimal places withround(). - Bonus: call
tip(bill, percent=15)and reprint the grand total with a 15% tip.
Bring tip_calculator.py next class. In PY-L1-30 we'll grow this into a full math toolbox — four returning functions you'll compose freely.
Sample · tip_calculator.py
# tip_calculator.py — return values in action def tip(bill, percent=10): return bill * percent / 100 def split(total, people): return total / people # main program bill = 84.00 people = 4 t = tip(bill) # default 10% grand = bill + t share = split(grand, people) print("Bill: RM", bill) print("Tip (10%): RM", round(t, 2)) print("Grand total: RM", round(grand, 2)) print("Per person: RM", round(share, 2)) # bonus — 15% tip version t15 = tip(bill, percent=15) print() print("With 15% tip: RM", round(bill + t15, 2))
Notice every function has exactly one return and no print. The main program is the one place that decides what to show the human. That's the "one job per function" pattern in action — the helpers compute, the main script displays.