Why it matters
A function that always does the same thing is of little use. A function that prints the same greeting every time cannot greet your friend by name, and a function that always prints 42 cannot tell you the answer for a different number. Parameters let you feed a function data at the moment you call it. Return values let a function hand an answer back, so the caller can store it, test it, or pass it to another function. Almost every useful function has one or both. In interviews you are almost always asked to write a function that takes inputs and returns an answer, so this lesson is where most of your practical function-writing skill starts.
The idea: input in, answer out
Think of a coffee machine. You choose the cup size and press a button. The cup size is the input, and the coffee that comes out is the result. The machine is the same for every cup. What changes is the input you give it, and so the output changes too.
A function works the same way. It has a set of steps, called its body. Some steps use values that come in from outside, through parameters. Some steps produce an answer, which goes back out through a return value. A function may have no inputs, no output, or both.
Two words are used very often here, and they are easy to mix up. A parameter is a name in the function's header, the name the function uses inside itself. An argument is the actual value you write in the call. In greet("Asha"), the text "Asha" is the argument. Inside greet, the parameter name holds the value. Python's error messages use the same two words, so the distinction helps when you read them.
Parameters: giving a function its input
To add a parameter, put a name inside the round brackets in the function header. Python does not ask for a type here. The name is how the function refers to the value inside its body, and the value can be any kind of object.
def greet(name):
print(f"Hello, {name}!")The header says that greet needs one value, and it will call that value name. A caller must supply it:
Output:
Hello, Asha!
Hello, Ravi!The same function body ran twice. The first time name held "Asha", and the second time it held "Ravi". Each call gets its own copy of the parameter name. The variable scope lesson explains where those names live.
The f"..." form is an f-string. Text inside the quotes is copied as it is, except for the parts inside curly braces, which Python replaces with the value of the expression. f"Hello, {name}!" builds one piece of text from the fixed words and the value of name. Inside an f-string, {x + 1} works too, because the braces hold any expression.
Several parameters and their order
A function can take more than one parameter. Separate them with commas. Python matches arguments to parameters by position by default. The first argument goes into the first parameter, and the second goes into the second. This is where many beginners make a quiet mistake. The program runs, but the answer is wrong because the values are in the wrong order. Consider a function that subtracts:
Output:
10 - 3 = 7
3 - 10 = -7Both calls are legal. Subtraction is not symmetric, so the order of arguments decides the answer. When you write a call with two numbers, check which number belongs in which position, and read the header to confirm. Good names help here. A header such as show_difference(larger, smaller) tells the caller what order to use.
Python also lets you pass an argument by name, which Java does not allow. Writing show_difference(b=3, a=10) is legal, and it gives the same answer as the second call above. Named arguments make a call readable when a function has several parameters. They are covered again in the variations section.
Returning a value
A function that computes an answer should return it. The word return followed by a value ends the function and hands the value back to the caller. Python does not need a return type in the header. The function simply returns whatever value the return line names.
def square(x):
return x * xThe body multiplies x by itself and hands the product back. The caller receives the value in place of the call. A call such as square(5) has the value 25 once it finishes, and that value can be used wherever a number can go.
A function can return different kinds of value, even from different paths. Here are two functions that return different types:
Output:
25
False
13n % 2 is the remainder after dividing by 2. The test == 0 is true when there is no remainder. The expression n % 2 == 0 is itself a value, True or False, so the function returns it directly. Python writes these two values with capital letters, True and False.
The last line shows that a call can sit inside a larger expression. Python first works out square(2), which is 4, and square(3), which is 9, and then adds them to get 13. Each call is replaced by its returned value before the addition runs.
Where a call can go
Once a function returns a value, the call behaves like that value. You can use it in many places. The table below lists the common ones.
| Place | Example | Result |
|---|---|---|
| Stored in a variable | s = square(5) | s holds 25 |
| Printed directly | print(square(5)) | prints 25 |
| Used in arithmetic | square(2) + square(3) | 13 |
| Used in a condition | if is_even(n): | the block runs when the result is True |
| Passed to another function | square(square(2)) | 16 |
The last row shows nesting. Python evaluates the inside call first, gets 4, and then passes 4 to the outer call, which returns 16. You can read it as "square of the square of 2".
A call can also stand on its own line, as in square(5). Python runs the function and then throws the answer away, because nothing stored or printed it. This is legal, but it is almost always a mistake, and a reader will wonder why you computed something you did not use.
return ends the function
When Python reaches a return statement, the function stops immediately. Any lines after the return, in that same path, never run. This is how you write an early exit.
def sign(n):
if n > 0:
return "positive"
if n < 0:
return "negative"
return "zero"Read the function one line at a time for the input -4. The first if tests n > 0, which is false, so its block is skipped. The second if tests n < 0, which is true, so the function returns "negative" and stops. The last line, return "zero", never runs for that input. Only the input 0 reaches it.
This style is called an early return. It keeps each case short and makes it easy to see every possible answer. The alternative, nesting if and else three levels deep, works too, but it is harder to read. Most Python programmers prefer the early return for simple cases like this.
Here is the function in a complete program. The program uses fixed values, so it runs without input:
Output:
-4 is negative
0 is zero
12 is positiveEach value takes a different path through the function, and each path ends at one return.
A function with no return gives None
In Python every function returns something. If the body never reaches a return line, the function returns the special value None. None means "no value". Look at this version of sign, which forgets the last case:
Output:
None
Traceback (most recent call last):
File "/path/to/main.py", line 9, in <module>
print(result + "!")
~~~~~~~^~~~~
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'For the input 0, neither if runs, and the function reaches its end. It returns None, and the print shows that. The next line tries to add text to None, and Python stops with a TypeError. The message names both types: NoneType is the type of None, and str is the type of the text.
This is the Python version of a mistake that Java catches at compile time. Python does not check at compile time that every path returns a value. The error appears only when the bad value is used, which can be far from the function that forgot the return. When you see NoneType in an error message, look for a function that was supposed to return a value and did not.
A function with no return at all is common, and it is fine when its job is to do something rather than to compute something. Such a function is often called a procedure in other languages. In Python it is just a function that returns None.
A bare return with no value also exits early and gives None. This is useful when a check at the top decides that there is nothing to do:
Output:
3Calling print_if_positive(-3) does nothing, and calling print_if_positive(3) prints 3.
Printing versus returning
Beginners often mix up these two actions, so it is worth separating them clearly. Printing shows a value to a person, on the screen. Returning gives a value to other code. A person reads a printed value, but other code cannot use it. Other code can use a returned value, but nobody sees it unless the caller prints it.
Compare two functions that add two numbers:
Output:
total is 50
9add_quiet gives the caller a number it can multiply by 10 before printing. add_loud prints its result and has no way to give that result to the rest of the program. If you later need the sum for a discount, add_quiet can be reused as it is, while add_loud would have to be rewritten.
The better design is nearly always to return the value and let the caller decide whether to print it. The function does its job and stays reusable. The caller, usually the code at the bottom of the file, is the place where output happens. Keep this in mind for every function you write in this module. A function that computes something should return it, and only the caller or a function named for printing should print.
Worked example 1: the larger of two numbers
The function larger returns the larger of two integers. Python has a built-in function called max that does this job, so this example uses a different name. Writing your own version is still useful, because it is the simplest example of a branch that returns.
def larger(a, b):
if a > b:
return a
return bTrace it for larger(8, 12). The test a > b asks whether 8 is greater than 12, which is false. So the first return is skipped, and return b gives 12. For larger(12, 8), the test is true, and return a gives 12 as well.
When two values are equal, the function returns b. Since the value is the same either way, that is correct. The function's behaviour is fully predictable.
Worked example 2: checking a prime number
A prime number is a whole number greater than 1 that has no divisors other than 1 and itself. The function is_prime returns True for primes and False otherwise. It uses a loop, and it returns early as soon as it finds a divisor.
Output:
True
FalseTrace it for n = 15. The first test, n < 2, is false. The variable i starts at 2. The condition i * i <= n is 4 <= 15, which is true. Then 15 % 2 is 1, not 0, so nothing happens and i becomes 3. Next, 3 * 3 <= 15 is true, and 15 % 3 is 0. The function returns False at this point and does not check any more values of i.
Why stop at the square root? If n has a divisor larger than its square root, it must also have one smaller than it, because the two multiply to n. So testing up to i * i <= n is enough. This is a common interview idea: checking up to the square root turns a slow search into a fast one.
The loop uses while here, with a counter that the body increases by hand. A for loop over range(2, int(n ** 0.5) + 1) does the same job. The range version is shorter, and the while version makes the stopping rule easy to see. Both are correct.
Worked example 3: a recursive digit sum
A function may call itself. This is called recursion, and it is a common tool in programming. You will use it in many later lessons. The idea is simple: a function solves a big problem by solving a smaller version of the same problem, and then combining the answer. To stop the chain, it needs a base case, which is a case it can answer directly without calling itself.
Take the digit sum of a number. The digit sum of 4096 is 4 + 0 + 9 + 6, which is 19. The last digit is n % 10, and everything before it is n // 10. The // operator is floor division, which gives a whole-number result. The digit sum of n is its last digit plus the digit sum of the rest.
Output:
digit sum of 4096 is 19
digit sum of 7 is 7The base case is n < 10. A single digit is its own digit sum, so the function returns n directly. Any bigger number leaves a smaller number to work on.
Trace sum_digits(4096). The function cannot answer directly, so it computes last as 6 and rest as 409, and then calls sum_digits(409) and waits. That call makes sum_digits(40), which makes sum_digits(4). The value 4 is below 10, so sum_digits(4) returns 4. Now the waiting calls finish in reverse. sum_digits(40) returns 0 + 4, which is 4. sum_digits(409) returns 9 + 4, which is 13. sum_digits(4096) returns 6 + 13, which is 19.
Going down the chain hands each smaller problem to the next call. Coming back up, each call adds its own last digit to the answer it received. Every recursive function has this two-part shape. Removing the base case is the classic bug, and the last section of this lesson shows what Python does then.
Worked example 4: a bus fare with several rules
Real functions often combine several rules. The example below computes a bus fare. The fare has a base part that grows with the distance, and two discounts based on age. Each rule is a separate step, and the function returns one final answer.
Output:
fare: 10Trace the function by hand. base_fare(5) is 10 + 2 * 5, which is 20. Then fare_for checks the age. The age 8 is less than 12, so the first if is true, and fare // 2 gives 10. The function returns at that point, and the second if is never reached.
The table below shows three calls, so you can see each rule fire.
| Call | Base fare | Rule that applies | Returned |
|---|---|---|---|
fare_for(8, 5) | 20 | under 12, so half price | 10 |
fare_for(30, 5) | 20 | no discount | 20 |
fare_for(65, 5) | 20 | 60 or over, so 70 percent | 14 |
Look closely at fare * 7 // 10 in the senior rule. Python works from left to right for multiplication and floor division, so it first computes 20 * 7, which is 140, and then divides by 10 with floor division, which gives 14.
Now compare the single slash. In Python / always gives a decimal result, even when both numbers are whole. 7 / 2 is 3.5, and 20 / 10 is 2.0. The fare rule above uses // because the bus company charges whole units. If the fare can have cents, use / and round the result on purpose. Java programmers should note this difference: in Python, / never throws away the decimal part by itself.
Two points make this function easy to trust. First, each rule is a separate if with its own return. A reader can see every possible answer by reading the function top to bottom. Second, base_fare has one job and can be tested alone. You could check base_fare(5) returns 20 without involving the age rules at all.
The order of the checks matters when two rules could both apply. In this fare they cannot, because no age is both under 12 and 60 or over. If the rules overlapped, the first if that is true would decide the answer, because its return ends the function. Whenever you write several rules, ask whether any input can satisfy two of them, and if one can, decide on purpose which rule should win.
Worked example 5: returning from inside a loop
A function often has to search for something, and the answer is the moment the search succeeds. A return inside a loop does exactly that. As soon as the answer is known, the function returns, and the rest of the loop is skipped. This search pattern shows up in many interview problems that ask "where is it?".
Here is a function that returns the position of a target value in a list, or -1 when the target is missing. The value -1 is a common choice for "not found", because no valid position is negative. Python also has a method list.index, which raises an error when the value is missing. Writing the search by hand shows the pattern.
Output:
1
-1Trace index_of([4, 8, 15], 8). The loop starts with i = 0. The test items[0] == 8 is false, so the loop moves on to i = 1. The test items[1] == 8 is true, so the function returns 1 at that moment. The final return -1 is never reached.
Trace index_of([4, 8, 15], 7). The loop checks all three cells, and none matches. The loop ends without returning, and the last line returns -1.
A caller uses the result by testing it:
pos = index_of(nums, 15)
if pos == -1:
print("not found")
else:
print("found at", pos)Passing values by assignment
When you pass a value into a function, the parameter name is bound to the same object the caller passed. For numbers and text this does not matter, because they cannot be changed in place. For a list it matters a great deal, because the function can change the list the caller owns. The lesson on references and memory covers this in full. For now, look at the difference:
Output:
5
['old', 'new']The number stayed 5, because n = n + 1 binds the name n to a new number inside the function and leaves x alone. The list changed, because append modifies the list object that both names point to. Plain numbers and text are safe to pass around, and lists need more care.
Default values and named arguments
A parameter can have a default value. If the caller leaves that argument out, the default is used. Defaults make a function easy to call in the common case, and they let the caller override the default when needed.
Output:
Hello, Asha!
Namaste, Asha!
Hi, Ravi!
Hello, Meena!The last two calls use names instead of positions. Named arguments can appear in any order, and they make the call read like a sentence. Once you use a named argument, every argument after it must be named too. A plain argument after a named one is a SyntaxError.
Parameters with defaults must come after parameters without defaults. def greet(greeting="Hello", name) is a SyntaxError, because Python cannot tell which argument fills which slot when the default comes first.
What happens inside a return
When a function reaches return, Python evaluates the expression after the word return, and then leaves the function. The value is handed to the caller. If the caller stores the value in a variable, the variable names the value. If the caller uses the value in an expression, Python uses it right away.
The return value does not stay in the function's frame. When the function finishes, its frame, with its parameters and local variables, is removed from the call stack. The value that was returned survives, because Python handed it over first. That is why it is safe for a function to return a local variable: the name goes away, but the value it held has already been handed to the caller.
Recursion makes the frames easy to see. Each call of sum_digits has its own n, last and rest. The calls never mix their numbers, because each frame keeps its own copy of the names. The stack has a size limit, and very deep recursion hits it.
Here is what the limit looks like. The function below calls itself with no base case:
Output (the number of repeated lines varies from one machine to another):
Traceback (most recent call last):
File "/path/to/main.py", line 4, in <module>
down(0)
~~~~^^^
File "/path/to/main.py", line 2, in down
return down(n + 1)
File "/path/to/main.py", line 2, in down
return down(n + 1)
File "/path/to/main.py", line 2, in down
return down(n + 1)
[Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceededPython stops the chain at a fixed depth, which is about 1000 calls by default. The error is RecursionError. The call stack lesson explains how the frames pile up and why the limit exists. The real fix is almost always a base case that the chain can reach.
Common mistakes and the messages you will see
Parameter and return mistakes produce clear messages, once you know how to read them. Here are the most common ones, with the messages Python 3.14 prints.
Missing argument. A function takes two values, and the call passes one.
TypeError: show_sum() missing 1 required positional argument: 'b'The message names the function and the missing parameter. Add the second argument, or give that parameter a default.
Too many arguments. A function takes one value, and the call passes two.
TypeError: square() takes 1 positional argument but 2 were givenCount the parameters in the header, and count the arguments in the call. They must match, unless some parameters have defaults.
Using the result of a function that returns None. The function forgot its return, and the caller uses the result.
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'Find the function named in the traceback and check that every path ends in a return with a value.
Mixing text and numbers. A caller adds a number to text.
TypeError: can only concatenate str (not "int") to strConvert the number with str(n) first, or use an f-string, which converts for you.
Unknown name. The call uses a function name that was never defined, or a name with a different spelling.
NameError: name 'squre' is not defined. Did you mean: 'square'?Python's hint suggests the closest name it knows. Compare the spelling character by character.
Wrong argument order. There is no message for this one. The program runs and prints the wrong answer, as the show_difference example showed. Check the order of arguments against the header whenever a result looks reversed.
Surprising division results. A function divides with / and the answer has a decimal point, such as 7.0, when you expected a whole number. Python's / always returns a decimal. Use // for whole-number division, and check which result the rest of your code expects.
A debugging walkthrough
Suppose a program prints average: 3 when you expected 3.5. The function is short:
def average(a, b):
return (a + b) // 2Call it as average(3, 4). The expected answer is 3.5. The function uses //, which is floor division, so (3 + 4) // 2 is 7 // 2, which gives 3. The decimal part is thrown away on purpose by that operator.
The fix is to use /, which keeps the decimal part. (3 + 4) / 2 gives 3.5. Run both versions and compare:
Output:
3
3.5
7.5This is a common bug, and it is easy to miss because both answers look like numbers. Check which operator the function uses, and check what the caller expects. When a number looks one unit off, suspect floor division.
Variations and edge cases
A function may take no parameters, one, or many. The more parameters, the more ways to pass them in the wrong order. Keep the list short where you can, and group related values into a single object once you learn classes later in the course.
Parameters can be any type of object, including text, booleans, decimals and lists. A list parameter is a special case, because the function can change the list the caller passed, as the earlier example showed.
Python has a shortcut for a variable number of arguments, written *args. It collects the extra positional arguments into a tuple. The varargs lesson in this module covers it in detail.
A default value must not be a mutable object such as a list, because Python creates the default once, when the function is defined, and every call shares that same object. This is a common trap:
Output:
['a']
['a', 'b']The second call did not start with an empty list. It reused the list from the first call. The safe pattern is to default to None and create the list inside the function. The references and memory lesson explains why the list is shared.
A function can return a single character or a single string. Python does not have a separate character type, so "a" is a string of length one, and both quote styles produce the same type. The message you see for a mismatch is the same TypeError shown above.
Overloading, which means two functions with the same name and different parameters, does not exist in Python. A second def with the same name replaces the first. The next lesson shows the Python ways to get the same effect.
Interview-style questions
An interviewer might ask, "What is the difference between a parameter and an argument?" A short answer is enough: the parameter is the name in the function header, and the argument is the value passed in the call.
They might ask, "Why should a function return a value instead of printing it?" The answer is reuse. A returned value can be stored, tested, combined and printed later. A printed value can only be read by a person.
A third common question is, "Write a function that returns whether a string is empty." This tests the return value. The answer returns a boolean with one expression, such as return len(s) == 0, or more simply return s == "". Strings are covered in a later module, but you can see the shape of the answer already.
Finally, expect a question about recursion: "What is the base case, and what happens without it?" Name the base case, explain that each call waits for the one it makes, and say that the chain never ends, so Python stops it with RecursionError.
Real-world uses
Parameters and return values are how almost every piece of software moves data. A payment system calls calculate_fee(amount, country) and receives a number. A search box calls normalize(text) and receives a cleaned string. A map app calls distance(lat1, lon1, lat2, lon2) and receives a distance. In each case, the caller decides the input and uses the answer, and the function only computes.
Teams rely on this pattern to test code. A function with inputs and a returned answer can be tested with a table of inputs and expected answers, without any screen or keyboard. This is why many professional Python codebases prefer functions that take inputs and return results, without changing anything outside themselves.
Practice
- Write
average(a, b)that returns the average of two numbers as a decimal. Call it with 7 and 8 and print the result. Predict the output before you run it.
Show the answer
def average(a, b):
return (a + b) / 2
print(average(7, 8))The output is 7.5. The / operator always gives a decimal result.
- Write
factorial(n)using a loop, wherefactorial(5)is 1 times 2 times 3 times 4 times 5. Test it with 5 and print the answer.
Show the answer
def factorial(n):
result = 1
for i in range(2, n + 1):
result = result * i
return result
print(factorial(5))The output is 120. The loop multiplies 1 by 2, then by 3, by 4 and by 5.
- Write
is_leap_year(year). A year is a leap year if it divides by 4, except that years divisible by 100 are not leap years unless they are also divisible by 400. Call it with 1900 and 2000, and predict the answers.
Show the answer
def is_leap_year(year):
if year % 400 == 0:
return True
if year % 100 == 0:
return False
return year % 4 == 0
print(is_leap_year(1900), is_leap_year(2000))The output is False True. 1900 is divisible by 100 but not by 400. 2000 is divisible by 400.
- Write
clamp(n, low, high)that returnslowifnis below it,highifnis above it, andnotherwise. Test it withclamp(15, 0, 10)andclamp(-3, 0, 10).
Show the answer
def clamp(n, low, high):
if n < low:
return low
if n > high:
return high
return n
print(clamp(15, 0, 10), clamp(-3, 0, 10))The output is 10 0.
- Write
count_digits(n)that returns how many digitsnhas, fornzero or greater. Use a loop that divides by 10. Predict the answer for4096and for0.
Show the answer
def count_digits(n):
count = 1
while n >= 10:
n = n // 10
count += 1
return count
print(count_digits(4096), count_digits(0))The output is 4 1. 0 has one digit, because the loop does not run and the count starts at 1.
- Predict the output of this program, then check it:
def twice(x):
return x * 2
print(twice(3) + twice(4))
print(twice(twice(3)))Show the answer
14
12twice(3) is 6 and twice(4) is 8, so the first line is 14. twice(twice(3)) is twice(6), which is 12.
- A function
area(w, h)returnsw * w + h. Predictarea(4, 2)andarea(2, 4), then explain why the order of arguments matters.
Show the answer
area(4, 2) is 4 * 4 + 2, which is 18. area(2, 4) is 2 * 2 + 4, which is 8. The order matters because the two parameters play different roles, and the caller must put each value in the right position.
- Explain what this function returns when called as
f(5), and why. Then say what the message is when the value is used inf(5) + 1.
def f(x):
if x > 10:
return xShow the answer
For f(5), the if is false, so the function reaches its end and returns None. The expression f(5) + 1 then fails with TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'. The fix is a return on the path that was missed, such as return x after the if.
- Write
grade(score)that returns"A"for 90 or more,"B"for 75 or more, and"C"otherwise. Call it with 92, 75 and 40.
Show the answer
def grade(score):
if score >= 90:
return "A"
if score >= 75:
return "B"
return "C"
print(grade(92), grade(75), grade(40))The output is A B C. The last return covers every other score.
- Write
sum_digits(n)recursively, as in the lesson, and call it with7and with1234. Name the base case in one sentence.
Show the answer
def sum_digits(n):
if n < 10:
return n
return n % 10 + sum_digits(n // 10)
print(sum_digits(7), sum_digits(1234))The output is 7 10. The base case is n < 10. A single digit is its own digit sum, so the function returns it without calling itself again.
What you now know
- A parameter is a named input in a function header. An argument is the value passed in the call.
- Arguments match parameters by position by default, and they can also be passed by name, so the order can be changed when names are given.
- A
returnhands a value back and ends the function at once. Early returns keep simple cases easy to read. - A function with no
returngivesNone, and using that value later fails with aTypeErrorthat can point far from the cause. - A function that computes should return its answer, and the caller decides whether to print it.
/always gives a decimal result, and//gives a whole-number result. Check which one a function needs.- A recursive function needs a base case, and it combines answers as the calls return. Without one, Python stops with
RecursionError. - The most common errors are
TypeErrorfor missing or extra arguments,TypeErrorwithNoneType,NameErrorfor unknown names, andRecursionError.