Weeks
Intro to PythonWeek 5 · Functions and Recursion
Week 5 Notes

Python,
Week 5

Functions: defining and calling them, return values, scope and the global keyword, argument types, and recursion, tied together with worked list and matrix examples.

8 chapters65 worked examples~59 min read
FunctionsreturnScopeArgumentsRecursion

Chapters

  1. 01Functions, Calls and returnParts 1-4
  2. 02Scope, Lifetime and globalParts 5-6
  3. 03Function Arguments and Types of FunctionsParts 7-8
  4. 04List FunctionsPart 9
  5. 05Sorting and Matrix Multiplication with FunctionsParts 10-11
  6. 06RecursionParts 12-13
  7. 07Try It YourselfPractice zone
  8. 08Week 5 Summary SheetAll parts
01
Functions

Functions, Calls and return

What a function is, how to define and call it, and what return does.

1.1What is a function?

Function

In Python, a function is a group of related statements that performs a specific task. Functions help break a program into smaller, modular chunks. As a program grows larger, functions make it more organised and manageable. Furthermore, they avoid repetition and make the code reusable.

Analogy. A function is like a recipe card for chai. You write the recipe once (water, tea, sugar, milk, in this order). After that, whenever somebody orders chai, you just follow the card with their sugar level. You never rewrite the recipe, and if you improve it you improve it in one place.

1.2Syntax of a function

Syntax

def function_name(parameters): followed by the indented statement(s).

A function definition consists of these components:

  • The keyword def, which marks the start of the function header.
  • A function name to uniquely identify the function. Function naming follows the same rules as writing variables in Python.
  • Parameters (arguments) through which we pass values to a function. They are optional.
  • A colon : to mark the end of the function header.
  • One or more valid Python statements that make up the function body. Statements must have the same indentation level.
  • An optional return statement to return a value from the function.
Python
def sums(a, b):
    c = a + b
    return c
Parts of a function definitiondef sums(a, b):    c = a + b    return cdefkeyword that starts the headersumsthe function name(a, b)parameters (optional):ends the headerbodyindented statementsreturnoptional: sends a value backthe body must be indented by the same amount
Figure 1. Parts of a function definition
Defining is not running. A def only stores the recipe. Nothing inside the body runs until the function is called.
Python
def hello():
    print("inside the function")
print("defined")
Output
defined

1.3How to call a function

Once we have defined a function, we can call it from another function, a program, or even the Python prompt. To call a function we simply type the function name with the appropriate arguments.

Python
def sums(a, b):
    c = a + b
    return c
print(sums(5, 6))
Output
11
Correction (extra). The source shows sums(5, 6) followed by print(sums) and says the output is 11. That is wrong: print(sums) (no brackets) prints the function object itself, as below. To see the answer, call the function inside print: print(sums(5, 6)). The same slip appears later with print(add).
Python
def sums(a, b):
    c = a + b
    return c
print(sums)
Output
<function sums at 0x1005bb060>

The long hexadecimal number is a memory address, so it is different on every machine and every run. What matters is that the brackets make the call: sums is the function, sums(5, 6) is its result.

Python
def sums(a, b):
    c = a + b
    return c
print(sums(sums(1, 2), 3))
print(sums("a", "b"))
Output
6
ab

Because Python does not fix the types, the same sums adds numbers or joins strings, and a call can be used inside another call (extra).

1.4The return statement

The return statement is used to exit a function and go back to the place from where it was called. It can contain an expression, which is evaluated and returned. If there is no expression, or no return at all, the function returns the None object.

Python
def greet(name):
    print("Hello" + name + ". Morning")
print(greet("Paul"))
Output
HelloPaul. Morning
None

None is the returned value, because greet() directly prints the name and has no return. So print(greet("Paul")) prints the greeting (from inside the function) and then None (the returned value).

Output check (extra). The source shows Hello Paul. Morning, with a space. The code builds the text with "Hello" + name + ". Morning", which has no space after Hello, so the real output is HelloPaul. Morning. (Write "Hello " + name to get the space.)
return and print are different jobsreturn: gives the value to the callerreturn csends the value back: 11print: only shows it on the screenprint(c)shows 11; the call gives Nonea function with no return gives back Noneprint(greet("Paul")) therefore prints the greeting and then None
Figure 2. return and print are different jobs
Python
def absolute_num(num):
    if num >= 0:
        return num
    else:
        return -num
print(absolute_num(2))
print(absolute_num(-4))
Output
2
4
  • A return ends the function at once (extra). Statements after it are never run.
  • Several values (extra). return a, b sends back both as a tuple, which can be unpacked into two variables.
Python
def f(x):
    return x * 2
    print("never printed")
print(f(4))
def both(a, b):
    return a + b, a - b
s, d = both(5, 2)
print(s, d)
print(both(5, 2))
Output
8
7 3
(7, 3)

1.5How a function works

When a function is called, control jumps into the function body, runs each statement in order, and, once it hits a return or the end of the function, jumps back to right after where it was called.

A call: control jumps into the function, then returns to just after the callmain programprint("start")r = sums(5, 6)print(r)def sums(a, b):c = a + breturn c1 jump in2 come backthe call is replaced by the returned value, 11
Figure 3. A call: control jumps into the function, then returns to just after the call
Python
def sums(a, b):
    print("  inside: adding", a, "and", b)
    c = a + b
    return c
print("1. start")
r = sums(5, 6)
print("2. back, r =", r)
print("3. end")
Output
1. start
  inside: adding 5 and 6
2. back, r = 11
3. end
StepWhereWhat happens
1mainprints "1. start"
2main → sumscall sums(5, 6): control jumps in; a = 5, b = 6
3sumsprints the "inside" line, computes c = 11
4sums → mainreturn c jumps back; the call becomes the value 11, stored in r
5mainprints "2. back, r = 11" and "3. end"

1.6Going deeper

  • Name, parameter, argument (extra). In def sums(a, b) the names a and b are parameters. In the call sums(5, 6) the values 5 and 6 are arguments. The source uses "parameters (arguments)" for both.
  • Define before you call (extra). The def line must have run before the call, so functions are usually defined at the top of the program.
Python
print(early(2))
def early(x):
    return x
Error
NameError: name 'early' is not defined

1.7Exam tips and common mistakes

Exam tips
  • For every function in a question first decide: does it print, return, or both?
  • When asked for the output of print(f(...)) write the lines printed inside f first, then the returned value.
Common mistakes
  • Forgetting the colon after the header, or the indentation of the body.
  • Writing print(f) when you meant print(f(...)).
  • Using print inside a function and then expecting a value back.
  • Writing statements after return and expecting them to run.

Practice questions (graded-assignment style)

Q1What is printed?

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def show(a):
    b = a + 1
x = show(6)
print(x)
1
7
2
None
3
7 then None
4
An error
Solution
Idea

The function returns nothing, so the call is None.

Python
def show(a):
    b = a + 1
x = show(6)
print(x)
Output
None
  • show has no return, so the call gives None and x is None.
  • The function itself prints nothing, so the only line printed is the None from print(x).
  • Answer: option 2.
Q2The returned value

Numerical Answer Type (NAT).

What does the program print?

Python
def sums(a, b):
    c = a + b
    return c
print(sums(5, 6))
Solution
Idea

Call the function inside print.

Python
def sums(a, b):
    c = a + b
    return c
print(sums(5, 6))
Output
11
  • sums(5, 6) returns 5 + 6 = 11.
  • Answer: 11
Q3Early return

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def absolute_num(num):
    if num >= 0:
        return num
    else:
        return -num
print(absolute_num(-4))
1
2
2
-4
3
4
4
2 and 4
Solution
Idea

absolute_num returns the positive version of the number.

Python
def absolute_num(num):
    if num >= 0:
        return num
    else:
        return -num
print(absolute_num(-4))
Output
4
  • -4 is negative, so the else branch returns -(-4) = 4.
  • Answer: option 3.
Q4Code after return

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def f(x):
    return x * 2
    print("never printed")
print(f(4))
1
8
2
8 and never printed
3
never printed
4
An error
Solution
Idea

A return ends the function.

Python
def f(x):
    return x * 2
    print("never printed")
print(f(4))
Output
8
  • The function returns 8 at the return line; the print after it never runs.
  • Answer: option 1.
Q5Two values

Numerical Answer Type (NAT).

What does the program print?

Python
def both(a, b):
    return a + b, a - b
print(both(5, 2))
Solution
Idea

return a, b gives a tuple.

Python
def both(a, b):
    return a + b, a - b
print(both(5, 2))
Output
(7, 3)
  • The function returns the pair (7, 3), and print shows it with brackets.
  • Answer: (7, 3)
Q6Function facts

Multiple Select Question (MSQ): select all that apply.

Choose the correct statements.

1
A function with no return statement returns None
2
def only defines a function; it does not run it
3
print(f) calls f and shows its result
4
return exits the function and goes back to the caller
Solution
Idea

Brackets make the call.

  • Statement 3 is false: print(f) shows the function object; print(f()) calls it.
  • Answer: options 1, 2 and 4.
Revision overview
Definedef name(params):
Colon and indented body
Defining does not run it
Callname(arguments)
Brackets make the call
print(f) shows the object
ReturnExits and sends a value back
No return gives None
return a, b gives a tuple
FunctionsChapter 1
WhyModular chunks
No repetition
Reusable
ControlCall: jump in
Return: jump back
The call becomes the value
print or returnprint shows
return gives back
Do not mix them up
Revision: what a function is, calling it and return on one page
Next · FunctionsScope, Lifetime and global
02
Functions

Scope, Lifetime and global

Which variables a function can see, how long they live, and how global changes things.

Where can a variable be used, and for how long does it live? These two ideas, scope and lifetime, explain most surprises people have with functions.

2.1Scope and lifetime of variables

Scope and lifetime

The scope of a variable is the portion of a program where the variable is recognised. Parameters and variables defined inside a function are not visible from outside the function, so they have a local scope.

The lifetime of a variable is the period throughout which it exists in memory. The lifetime of variables inside a function is as long as the function executes. They are destroyed once we return from the function. So a function does not remember the value of a variable from its previous calls.

Analogy. Think of a hotel room. What you put in the room is yours while you stay, and housekeeping clears it when you check out. Nobody in the lobby can see inside, and the next guest does not find your things. A function's local variables are the room; the lobby is the outside program.
Python
def my_func():
    x = 10
    print("Value inside the function:", x)
x = 20
my_func()
print("Value outside function:", x)
Output
Value inside the function: 10
Value outside function: 20

The outside value is still 20. The x inside my_func() is a different variable (local to the function) from the x outside. They have the same name but different scopes. (The source says the function "changed" x to 10; more exactly, the assignment created a new local x and left the outer one alone.)

Two variables named x live in two different placesoutside (global scope)x = 20stays 20def my_func():x = 10local: gone when the function endsthe function changed ITS x, not the outer xso the outside still prints 20
Figure 4. Two variables named x live in two different places

A function forgets between calls

Python
def count():
    c = 0
    c += 1
    return c
print(count())
print(count())
print(count())
Output
1
1
1

Each call starts with a fresh c = 0, so the answer never grows. To keep a count between calls it has to live outside the function (next section).

2.2Reading a variable from outside

Variables outside a function are visible from inside: they have global scope. We can read these values inside the function, but we cannot change (write) them.

Python
rate = 0.18
def with_tax(price):
    return price + price * rate
print(with_tax(100))
Output
118.0

rate is read inside the function without any trouble. But the moment a function assigns to a name, Python treats that name as local for the whole function (extra). That makes the following program fail:

Python
x = 5
def f():
    x = x + 1
    return x
print(f())
Error
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value

Inside f, x = x + 1 makes x local, and the local x has no value yet when the right-hand side is read. The cure is the global keyword.

2.3The global keyword

To modify the value of a variable outside the function, it must be declared as a global variable using the keyword global.

Python
def myFunction1():
    global x
    x = x * 2
    print("Value of x in function 1", x)
def myFunction2():
    global x
    x = x * 3
    print("Value of x in function 2", x)
x = 5
print("Value of x before function call", x)
myFunction1()
myFunction2()
print("Value of x after function call", x)
Output
Value of x before function call 5
Value of x in function 1 10
Value of x in function 2 30
Value of x after function call 30
With global x both functions change the same outside variablex = 5startx = 5myFunction1()x = x * 2x = 10myFunction2()x = x * 3x = 30after both callsx = 305 → 10 → 30without the global line the functions could not change x
Figure 5. With global x both functions change the same outside variable
StepStatementx after
1x = 55
2myFunction1(): x = x * 210
3myFunction2(): x = x * 330
  • global x is written inside the function, before x is used there.
  • Only assignment needs it (extra). A list defined outside can be changed with methods like append without global, because the name is not being re-assigned.
  • Prefer parameters and return (extra). Passing values in and returning results keeps functions independent and easier to test. Use global sparingly.
Python
items = []
def add_item(x):
    items.append(x)
add_item(1)
add_item(2)
print(items)
Output
[1, 2]

2.4Going deeper

  • Parameters are local too (extra). Changing a parameter inside the function does not change the caller's variable (for a number or string). Changing the contents of a list passed in does change it, as Chapter 5 shows.
  • Name lookup order (extra). Python looks for a name first inside the function, then outside. A local name hides a global one with the same name.
Python
def bump(n):
    n = n + 1
    return n
x = 10
print(bump(x))
print(x)
Output
11
10

2.5Exam tips and common mistakes

Exam tips
  • For scope questions list the variables in two columns: inside the function, outside. Same name does not mean same variable.
  • A global line is the clue that the outside variable is meant to change.
Common mistakes
  • Expecting a local assignment to change the outside variable.
  • Expecting a function to remember a local variable from its last call.
  • Forgetting global and then getting UnboundLocalError.

Practice questions (graded-assignment style)

Q1Inside and outside

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
x = 5
def show():
    x = 100
    print("Inside:", x)
show()
print("Outside:", x)
1
Inside: 100, Outside: 100
2
Inside: 100, Outside: 5
3
Inside: 5, Outside: 5
4
An error
Solution
Idea

The function makes its own local x.

Python
x = 5
def show():
    x = 100
    print("Inside:", x)
show()
print("Outside:", x)
Output
Inside: 100
Outside: 5
  • The assignment inside show creates a local x; the global x is untouched.
  • Answer: option 2.
Q2The global counter

Numerical Answer Type (NAT).

What does the program print?

Python
n = 0
def f():
    global n
    n += 1
f()
f()
f()
print(n)
Solution
Idea

Each call adds 1 to the same variable.

Python
n = 0
def f():
    global n
    n += 1
f()
f()
f()
print(n)
Output
3
  • Three calls add 1 each time to the same global n: 0 → 1 → 2 → 3.
  • Answer: 3
Q3No memory

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def count():
    c = 0
    c += 1
    return c
print(count(), count(), count())
1
1 2 3
2
1 1 1
3
3 3 3
4
0 0 0
Solution
Idea

A local variable is created fresh on each call.

Python
def count():
    c = 0
    c += 1
    return c
print(count(), count(), count())
Output
1 1 1
  • c is reset to 0 at the start of every call, so each call returns 1.
  • Answer: option 2.
Q4Global x over two calls

Numerical Answer Type (NAT).

What does the program print?

Python
def f():
    global x
    x = x * 2
def g():
    global x
    x = x * 3
x = 5
f()
g()
print(x)
Solution
Idea

First doubles, then triples.

Python
def f():
    global x
    x = x * 2
def g():
    global x
    x = x * 3
x = 5
f()
g()
print(x)
Output
30
  • 5 × 2 = 10, then 10 × 3 = 30.
  • Answer: 30
Q5A parameter changes

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def bump(n):
    n = n + 1
    return n
x = 10
print(bump(x), x)
1
11 11
2
11 10
3
10 11
4
10 10
Solution
Idea

Changing a number parameter does not change the caller's variable.

Python
def bump(n):
    n = n + 1
    return n
x = 10
print(bump(x), x)
Output
11 10
  • bump(x) returns 11, but the variable x outside is still 10.
  • Answer: option 2.
Q6Scope facts

Multiple Select Question (MSQ): select all that apply.

Choose the correct statements.

1
Variables defined inside a function have local scope
2
A function remembers its local variables from the previous call
3
global lets a function modify a variable defined outside
4
A global variable can be read inside a function without global
Solution
Idea

Local variables vanish when the function returns.

  • Statement 2 is false: local variables are destroyed on return.
  • Answer: options 1, 3 and 4.
Revision overview
Local scopeParameters and inner variables
Not visible outside
Destroyed on return
Global scopeOuter variables are readable
Not writable by default
Same name is not same variable
global xLets a function modify x
Written inside the function
Needed for assignment only
ScopeChapter 2
LifetimeExists while the function runs
No memory between calls
ErrorUnboundLocalError
when you assign without global
Better stylePass values in
Return results out
Revision: scope and global on one page
Next · FunctionsFunction Arguments and Types of Functions
03
Functions

Function Arguments and Types of Functions

Wrong counts, default and keyword arguments, and the kinds of functions.

How many values must a call give, and in what order? This chapter covers wrong argument counts, default arguments, keyword arguments, and the different kinds of functions you can call.

3.1Types of function arguments

Python
def add(a, b):
    ans = a + b
    return ans
print(add(4, 2))
Output
6

Here add() has two parameters. Since we called it with two arguments, it runs smoothly. If we call it with a different number of arguments, the interpreter shows an error message.

One argument

Python
def add(a, b):
    ans = a + b
    return ans
print(add(4))
Error
TypeError: add() missing 1 required positional argument: 'b'

No arguments

Python
def add(a, b):
    ans = a + b
    return ans
print(add())
Error
TypeError: add() missing 2 required positional arguments: 'a' and 'b'

Too many arguments (extra)

Python
def add(a, b):
    ans = a + b
    return ans
print(add(1, 2, 3))
Error
TypeError: add() takes 2 positional arguments but 3 were given
Correction (extra). The source writes add(4, 2) followed by print(add) and shows 6 as the output. As in Chapter 1, print(add) would print the function object. The value 6 comes from print(add(4, 2)).

Python has different kinds of function arguments to deal with such situations.

3.2Default arguments

Function arguments can have default values. We give a default to an argument with the assignment operator =.

Python
def add(a, b=10):
    ans = a + b
    return ans
print(add(4))
print(add(2, 3))
Output
14
5

Here a has no default and is required in a call. b has the default 10, so it is optional: if a value is provided, it overwrites the default.

The ordering rule

Any number of arguments can have a default value. But once an argument has a default, all the arguments to its right must also have defaults: non-default arguments cannot follow default arguments.

Python
def add(a=10, b):
    ans = a + b
    return ans
Error
SyntaxError: parameter without a default follows parameter with a default
Message wording (extra). The source quotes the older text non-default argument follows default argument. Newer Python versions say parameter without a default follows parameter with a default, as in the real output above. It is the same SyntaxError, so both mean the rule was broken.

3.3Keyword arguments

When we call a function with values, they are assigned to the parameters by position. Python also allows keyword arguments: name=value in the call. This way the order of the arguments can be changed. In add(4, 2) the value 4 goes to a and 2 to b. The calls below are all valid:

Python
def add(a, b):
    ans = a + b
    return ans
print(add(a=2, b=4))
print(add(b=9, a=3))
print(add(4, b=2))
Output
6
12
6
How the values reach a and bdef add(a, b=10):add(4)a = 4, b = 10→ 14b uses its defaultadd(2, 3)a = 2, b = 3→ 5the value overrides the defaultadd(b=9, a=3)a = 3, b = 9→ 12keywords: any orderadd(4, b=2)a = 4, b = 2→ 6positional first, then keyworddef add(a=10, b):SyntaxError: default before non-defaultadd(a=4, 2)SyntaxError: positional after keyword
Figure 6. How the values reach a and b
The rule for mixing
  • We can mix positional and keyword arguments, but keyword arguments must follow positional ones. A positional argument after a keyword argument is an error.
  • Positional aur keyword arguments mila sakte hain, par keyword arguments positional ke baad aane chahiye. Keyword ke baad positional argument error hai.
Python
def add(a, b):
    ans = a + b
    return ans
print(add(a=4, 2))
Error
SyntaxError: positional argument follows keyword argument
  • Extra: a name cannot get two values. add(1, a=2) gives a a value twice.
  • Extra: unknown keywords fail. add(1, c=2) uses a name the function does not have.
Python
def add(a, b):
    ans = a + b
    return ans
print(add(1, a=2))
Error
TypeError: add() got multiple values for argument 'a'
Python
def add(a, b):
    ans = a + b
    return ans
print(add(1, c=2))
Error
TypeError: add() got an unexpected keyword argument 'c'

Why keywords help. In a call like address(pin="600001", city="Chennai", state="TN") the names make the call readable, and the order no longer matters.

3.4Types of functions

KindExamplesWhere it comes from
In-built functionsprint(), input(), len()always available
Library functionslog(), sqrt(), random(), randrange(), calendar(), month()need an import first
String methodsupper(), lower(), strip(), count(), index(), replace()called on a string with a dot
User-defined functionsthe ones you write with defyour own code
Python
def square(x):
    sqr = x ** 2
    return sqr
print(square(5))
Output
25
Python
import math
import random
print(len([4, 8, 2]))
print(math.sqrt(16))
random.seed(1)
print(random.randrange(1, 7))
s = "  Hello World  "
print(s.strip().upper())
print(s.count("o"), s.strip().index("W"), s.replace("World", "Python").strip())
Output
3
4.0
2
HELLO WORLD
2 6 Hello Python

Notice the difference in how they are called: len(x) and square(5) are plain function calls; math.sqrt(16) uses a module name; s.upper() is a method called on the object s. The random.seed(1) line only makes the random number repeatable (extra).

3.5Going deeper

  • Defaults are evaluated once (extra). Use simple values (numbers, strings, None) as defaults. A list as a default (def f(x, l=[])) is created only once and is shared by every call, which causes surprising bugs.
  • Which style when (extra). Use defaults for settings people rarely change, and keywords when a call has many arguments of the same type.
Python
def add_to(x, l=[]):
    l.append(x)
    return l
print(add_to(1))
print(add_to(2))
Output
[1]
[1, 2]

3.6Returning several values, *args, docstrings and two traps with lists

A. Returning more than one value

return a, b returns one tuple holding both values. The caller usually unpacks it into two names. A function that reaches its end without return gives back None.

Python
def min_max(a, b):
    if a < b:
        return a, b
    return b, a

print(min_max(3, 2))           # one tuple
lo, hi = min_max(3, 2)         # unpacked
print(lo, hi)

def show(x):
    print(x * 2)               # prints, but returns nothing

r = show(0)                    # prints 0
print(r)                       # ... and r is None
Output
(2, 3)
2 3
0
None
print versus return. print only shows a value on the screen; return hands it back so the caller can use it. A function used inside an expression (3 + f(2)) must return, otherwise the expression meets None and fails with a TypeError.

B. Any number of arguments: *args

A parameter written *nums collects all the extra positional arguments into a tuple called nums. The function then works for two arguments, ten, or none:

Python
def total(*nums):
    s = 0
    for n in nums:
        s = s + n
    return s

print(total())                 # no arguments: nums is ()
print(total(1, 2, 3))
def show_args(*nums):
    return nums
print(show_args(1, 2, 3))
Output
0
6
(1, 2, 3)

C. Docstrings

A string placed on the first line inside a function is its docstring: a description that tools (and help()) can display. It does not run, and unlike a # comment it is stored in f.__doc__.

Python
def square(x):
    """Return the square of x."""
    return x * x

print(square.__doc__)
print(square(5))
Output
Return the square of x.
25

D. Trap 1: a list default argument is created once

Default values are evaluated once, when the def line runs. A default list is therefore one shared list that keeps growing between calls:

Python
def add_item(x, items=[]):     # the list is created once
    items.append(x)
    return items

print(add_item(1))
print(add_item(2))             # the same list as before
print(add_item(3, [9]))        # passing your own list avoids the sharing
print(add_item(4))
Output
[1]
[1, 2]
[9, 3]
[1, 2, 4]

The fix is to use None as the default and build a fresh list inside:

Python
def add_item(x, items=None):
    if items is None:
        items = []
    items.append(x)
    return items

print(add_item(1))
print(add_item(2))
Output
[1]
[2]

E. Trap 2: a function can change the caller's list, but not by rebinding

A list passed to a function is the same list (the parameter is another name for it). Changing it with append, sort, l[i] = ... shows up outside. l = l + [4] builds a new list and only rebinds the local name, so the caller's list is untouched. This is the "two names, one list" idea of week 4, now across a function call.

Python
def change_in_place(l):
    l.append(4)

def rebind(l):
    l = l + [4]                # a new list; the caller never sees it

a = [1]
change_in_place(a)
b = [1]
rebind(b)
print(a, b)
Output
[1, 4] [1]

3.7Exam tips and common mistakes

Exam tips
  • Match arguments to parameters: first by position, then by keyword. Parameters left over take their defaults.
  • A "which call is invalid?" question usually hides a positional-after-keyword or a missing/extra argument.
Common mistakes
  • Putting a default parameter before a non-default one.
  • Giving a positional argument after a keyword argument.
  • Calling with the wrong number of arguments.

Practice questions (graded-assignment style)

Q1Default used

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def add(a, b=10):
    return a + b
print(add(4))
1
4
2
14
3
10
4
An error
Solution
Idea

b falls back to its default.

Python
def add(a, b=10):
    return a + b
print(add(4))
Output
14
  • add(4) gives a = 4 and the default b = 10, so the result is 14.
  • Answer: option 2.
Q2Keywords out of order

Numerical Answer Type (NAT).

What does the program print?

Python
def add(a, b=10):
    return a + b
print(add(b=9, a=3))
Solution
Idea

Keywords match names, not positions.

Python
def add(a, b=10):
    return a + b
print(add(b=9, a=3))
Output
12
  • b=9 and a=3 give 3 + 9 = 12.
  • Answer: 12
Q3Which error?

Multiple Choice Question (MCQ): choose the one correct option.

What happens when the call runs?

Python
def add(a, b):
    return a + b
print(add(4))
1
It prints 4
2
TypeError: missing 1 required positional argument
3
SyntaxError
4
It prints None
Solution
Idea

b has no default.

Python
def add(a, b):
    return a + b
print(add(4))
Error
TypeError: add() missing 1 required positional argument: 'b'
  • One required argument is missing, so Python raises a TypeError.
  • Answer: option 2.
Q4Positional after keyword

Multiple Choice Question (MCQ): choose the one correct option.

What happens when the program runs?

Python
def add(a, b):
    return a + b
print(add(a=4, 2))
1
It prints 6
2
It prints 2
3
SyntaxError
4
TypeError
Solution
Idea

Keyword arguments must follow positional ones.

Python
def add(a, b):
    return a + b
print(add(a=4, 2))
Error
SyntaxError: positional argument follows keyword argument
  • A positional argument after a keyword argument is rejected when the program is read, as a SyntaxError.
  • Answer: option 3.
Q5Mixed call

Numerical Answer Type (NAT).

What does the program print?

Python
def add(a, b=10):
    return a + b
print(add(4, b=2))
Solution
Idea

The positional 4 goes to a; the keyword fills b.

Python
def add(a, b=10):
    return a + b
print(add(4, b=2))
Output
6
  • a = 4 by position and b = 2 by keyword: 4 + 2 = 6.
  • Answer: 6
Q6Argument rules

Multiple Select Question (MSQ): select all that apply.

Choose the correct statements.

1
After a default parameter all later parameters need defaults
2
Keyword arguments must come before positional ones
3
A value passed for a defaulted parameter overrides the default
4
Keyword arguments let you change the order of the arguments
Solution
Idea

Keyword arguments follow positional ones.

  • Statement 2 is false: keyword arguments must come after the positional ones.
  • Answer: options 1, 3 and 4.
Q1What a function returns

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def f(a, b):
    return a + b, a * b

x = f(2, 5)
print(x)
1
7
2
(7, 10)
3
[7, 10]
4
7 10
Solution
Idea

return a, b returns one tuple; printing a tuple shows its brackets and commas.

Python
def f(a, b):
    return a + b, a * b

x = f(2, 5)
print(x)
Output
(7, 10)
  • 2+5=72 + 5 = 7 and 2×5=102 \times 5 = 10 packed as (7, 10). Option 4 would come from print(*x) or a, b = f(2, 5) followed by print(a, b).
  • Answer: option 2.
Q2Print versus return

Multiple Choice Question (MCQ): choose the one correct option.

What happens when this program runs?

Python
def double(x):
    print(x * 2)

y = 10 + double(3)
print(y)
1
prints 6 then 16
2
prints 16
3
prints 6, then raises a TypeError
4
prints 6 then None
Solution
Idea

double prints but returns None, so 10 + double(3) is 10 + None.

Python
def double(x):
    print(x * 2)

y = 10 + double(3)
print(y)
Output
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
  • The call runs first and prints 6. Then 10 + None is not allowed, so the program stops with a TypeError and never reaches the last print.
  • Changing print(x * 2) to return x * 2 would give 16.
  • Answer: option 3.
Q3Using *args

Numerical Answer Type (NAT).

What does the program print?

Python
def count_big(*nums):
    c = 0
    for n in nums:
        if n > 10:
            c += 1
    return c

print(count_big(5, 12, 30, 7, 11))
Solution
Idea

nums is the tuple (5, 12, 30, 7, 11).

Python
def count_big(*nums):
    c = 0
    for n in nums:
        if n > 10:
            c += 1
    return c

print(count_big(5, 12, 30, 7, 11))
Output
3
  • The values above 10 are 12, 30 and 11. Three.
  • Answer: 3
Q4The shared default list

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def add(x, l=[]):
    l.append(x)
    return l

add(1)
add(2)
print(add(3))
1
[3]
2
[1, 2, 3]
3
[1, 2]
4
[]
Solution
Idea

The default list is created once and shared by every call that does not pass its own list.

Python
def add(x, l=[]):
    l.append(x)
    return l

add(1)
add(2)
print(add(3))
Output
[1, 2, 3]
  • Call 1 puts 1 into the shared list, call 2 adds 2, call 3 adds 3. The list is [1, 2, 3].
  • Option 1 is what you would expect if each call started with a fresh empty list.
  • Answer: option 2.
Q5Does the caller see the change?

Multiple Select Question (MSQ): select all that apply.

After the program runs, select the correct statements.

Python
def f1(l):
    l.append(9)

def f2(l):
    l = l + [9]

def f3(l):
    l[0] = 5

a = [1, 2]
b = [1, 2]
c = [1, 2]
f1(a)
f2(b)
f3(c)
1
a is [1, 2, 9]
2
b is [1, 2, 9]
3
b is [1, 2]
4
c is [5, 2]
Solution
Idea

Changing the list the name points to is seen by the caller. Pointing the local name at a new list is not.

Python
def f1(l):
    l.append(9)

def f2(l):
    l = l + [9]

def f3(l):
    l[0] = 5

a = [1, 2]
b = [1, 2]
c = [1, 2]
f1(a)
f2(b)
f3(c)
print(a, b, c)
Output
[1, 2, 9] [1, 2] [5, 2]
  • 1 ✓ append changes the shared list. 2 ✗ and 3 ✓: l + [9] makes a new list bound only to the local l. 4 ✓ item assignment changes the shared list.
  • Answer: options 1, 3 and 4.
Revision overview
Count must matchToo few: missing argument
Too many: takes N but M given
TypeError
Defaultdef f(a, b=10)
Optional: b
Defaults go last
Keywordf(b=9, a=3)
Any order
After positional ones
ArgumentsChapter 3
Function kindsIn-built, library
String methods
User-defined
ErrorsSyntaxError: default before non-default
SyntaxError: positional after keyword
CareNo list as a default
print(add) is not a call
Revision: function arguments and types on one page
Next · ExamplesList Functions
04
Examples

List Functions

Minimum, maximum, joining and average of lists as functions.

Functions shine when they wrap a small, useful job. Here are five list functions (minimum, maximum, two ways of joining lists, and the average), and a sixth that uses two of them.

4.1The five functions

Find the minimum in a list

Python
def list_min(l):
    mini = l[0]
    for i in range(len(l)):
        if (l[i] < mini):
            mini = l[i]
    return mini

Find the maximum in a list

Python
def list_max(l):
    maxi = l[0]
    for i in range(len(l)):
        if (l[i] > maxi):
            maxi = l[i]
    return maxi

Append a list before a given list

Python
def list_appendBefore(l, z):
    newl = []
    for i in range(len(z)):
        newl.append(z[i])
    for i in range(len(l)):
        newl.append(l[i])
    return newl

Append a list after a given list

Python
def list_appendEnd(l, z):
    newl = []
    for i in range(len(l)):
        newl.append(l[i])
    for i in range(len(z)):
        newl.append(z[i])
    return newl

Average of all list elements

Python
def list_average(l):
    sum = 0
    for i in range(len(l)):
        sum = sum + l[i]
    ans = sum / len(l)
    return ans
  • list_min / list_max. Assume the first item is the answer, then replace it whenever a smaller (or larger) item is seen. It is the same loop as Week 4, now wrapped in a function.
  • list_appendBefore / list_appendEnd. Both build a new list newl by copying two lists in a different order; the original lists are left untouched.
  • list_average. Add everything up in sum, then divide by len(l).

4.2Calling our functions

Using l = [1, 8, 4, 3, 2, 9] and z = [10, 16, 20]:

Python
l = [1, 8, 4, 3, 2, 9]
z = [10, 16, 20]
print(list_min(l))
print(list_max(l))
print(list_appendBefore(l, z))
print(list_appendEnd(l, z))
print(list_average(l))
Output
1
9
[10, 16, 20, 1, 8, 4, 3, 2, 9]
[1, 8, 4, 3, 2, 9, 10, 16, 20]
4.5
CallOutputWhy
list_min(l)1the smallest item
list_max(l)9the largest item
list_appendBefore(l, z)[10, 16, 20, 1, 8, 4, 3, 2, 9]z first, then l
list_appendEnd(l, z)[1, 8, 4, 3, 2, 9, 10, 16, 20]l first, then z
list_average(l)4.527 / 6

4.3Things worth noticing (extra)

  • The list is not changed. None of the five functions modifies l or z; they only read them, and the two joining functions return a new list.
  • sum hides a built-in. Naming a variable sum works but shadows Python's own sum() inside that function. A name like total is safer.
  • Empty lists break them. l[0] on an empty list is an IndexError, and the average of an empty list divides by zero.
  • Built-ins do the same (extra). min(l), max(l), sum(l) / len(l) and l + z give the same answers. Writing the loops yourself is the exam skill.
Python
l = [1, 8, 4, 3, 2, 9]
z = [10, 16, 20]
new = list_appendEnd(l, z)
print(l, z)
print(min(l), max(l), sum(l) / len(l), l + z == new)
Output
[1, 8, 4, 3, 2, 9] [10, 16, 20]
1 9 4.5 True
Python
print(list_average([]))
Error
ZeroDivisionError: division by zero

4.4Functions calling functions

Example 7The range of a list

Write list_range(l) that returns the largest item minus the smallest item, by using list_max and list_min.

Solution
Python
def list_range(l):
    return list_max(l) - list_min(l)
print(list_range([1, 8, 4, 3, 2, 9]))
print(list_range([5, 5, 5]))
Output
8
0

A function can call other functions you have written. The big job (list_range) is now one line, because the small jobs are already solved and tested.

Example 8Count the items above the average

Write count_above_average(l) using list_average.

Solution
Python
def count_above_average(l):
    avg = list_average(l)
    count = 0
    for x in l:
        if x > avg:
            count += 1
    return count
print(count_above_average([1, 8, 4, 3, 2, 9]))
Output
2

The average of [1, 8, 4, 3, 2, 9] is 4.5, and the items above it are 8 and 9, so the answer is 2.

4.5Exam tips and common mistakes

Exam tips
  • Say what each function takes and returns before writing it: "takes a list, returns a number".
  • Start the answer variable from the first item (l[0]) for min/max, and from 0 for sums.
Common mistakes
  • Putting return inside the loop, which ends the function after the first item.
  • Printing the answer inside the function instead of returning it.
  • Dividing by len(l) without checking for an empty list.

Practice questions (graded-assignment style)

Q1The minimum

Numerical Answer Type (NAT).

What does the program print?

Python
def list_min(l):
    mini = l[0]
    for i in range(len(l)):
        if (l[i] < mini):
            mini = l[i]
    return mini
print(list_min([7, 3, 9, 2, 8]))
Solution
Idea

Start with the first item and keep the smaller.

Python
def list_min(l):
    mini = l[0]
    for i in range(len(l)):
        if (l[i] < mini):
            mini = l[i]
    return mini
print(list_min([7, 3, 9, 2, 8]))
Output
2
  • The smallest of [7, 3, 9, 2, 8] is 2.
  • Answer: 2
Q2The average

Numerical Answer Type (NAT).

What does the program print?

Python
def list_average(l):
    total = 0
    for x in l:
        total = total + x
    return total / len(l)
print(list_average([2, 4, 6, 8]))
Solution
Idea

Total divided by the count.

Python
def list_average(l):
    total = 0
    for x in l:
        total = total + x
    return total / len(l)
print(list_average([2, 4, 6, 8]))
Output
5.0
  • (2 + 4 + 6 + 8) / 4 = 20 / 4 = 5.0. Division always gives a float.
  • Answer: 5.0
Q3Joined lists

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def list_appendBefore(l, z):
    newl = []
    for i in range(len(z)):
        newl.append(z[i])
    for i in range(len(l)):
        newl.append(l[i])
    return newl
print(list_appendBefore([1, 2], [3, 4]))
1
[1, 2, 3, 4]
2
[3, 4, 1, 2]
3
[4, 3, 2, 1]
4
[[1, 2], [3, 4]]
Solution
Idea

appendBefore(l, z) puts z first.

Python
def list_appendBefore(l, z):
    newl = []
    for i in range(len(z)):
        newl.append(z[i])
    for i in range(len(l)):
        newl.append(l[i])
    return newl
print(list_appendBefore([1, 2], [3, 4]))
Output
[3, 4, 1, 2]
  • z = [3, 4] is copied first, then l = [1, 2].
  • Answer: option 2.
Q4Return in the loop

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def biggest(l):
    big = l[0]
    for x in l:
        if x > big:
            big = x
        return big
print(biggest([1, 9, 5]))
1
9
2
5
3
1
4
3
Solution
Idea

A return ends the function at once.

Python
def biggest(l):
    big = l[0]
    for x in l:
        if x > big:
            big = x
        return big
print(biggest([1, 9, 5]))
Output
1
  • The loop returns on its first pass, so only the first item, 1, is ever looked at. This is the "return inside the loop" mistake.
  • Answer: option 3.
Q5The range of a list

Numerical Answer Type (NAT).

list_range(l) returns list_max(l) - list_min(l). What does it return for [1, 8, 4, 3, 2, 9]?

Solution
Idea

The largest is 9, the smallest is 1.

  • 9 - 1 = 8.
  • Answer: 8
Q6Function facts

Multiple Select Question (MSQ): select all that apply.

Choose the correct statements about the five list functions.

1
They do not change the list they are given
2
list_appendBefore and list_appendEnd return a new list
3
list_average of an empty list returns 0
4
list_min starts from the first item of the list
Solution
Idea

An empty list makes the average divide by zero.

  • Statement 3 is false: it raises a ZeroDivisionError.
  • Answer: options 1, 2 and 4.
Revision overview
Min and maxStart from l[0]
Replace when smaller or larger
return at the end
JoiningNew list newl
Copy two lists in order
Originals untouched
AverageTotal / len(l)
27 / 6 = 4.5
Empty list: error
List functionsChapter 4
Building uplist_range uses list_max and list_min
Carefulreturn outside the loop
sum shadows the built-in
Built-insmin, max, sum, l + z
give the same answers
Revision: five list functions on one page
Next · ExamplesSorting and Matrix Multiplication with Functions
05
Examples

Sorting and Matrix Multiplication with Functions

Two bigger programs rebuilt from small helper functions.

Two bigger programs from Week 4, rebuilt out of small functions: selection sort, and matrix multiplication. The idea: break a problem into smaller modules and solve them separately. That makes the whole problem much easier on the mind.

5.1Sorting using functions

The idea: find the smallest element in the list, append it to a new list, remove it from the original list, and repeat until the original list is empty. Finding the minimum is its own job, so it gets its own function.

Python
def min_list(l):
    mini = l[0]
    for i in range(len(l)):
        if (l[i] < mini):
            mini = l[i]
    return mini

def obvious_sort(l):
    x = []
    while (len(l) > 0):
        mini = min_list(l)
        x.append(mini)
        l.remove(mini)
    return x
Python
print(obvious_sort([12, 10, 7, 18, 6, 42, 8, 5, 35, 7]))
Output
[5, 6, 7, 7, 8, 10, 12, 18, 35, 42]
obvious_sort asks min_list for the minimum each roundobvious_sort(l)moves it to x, removes it from lmin_list(l)finds the smallest itemlsmallestsplitting the job makes each part easier to think aboutbut note: l.remove(...) empties the list you passed in
Figure 7. obvious_sort asks min_list for the minimum each round
Roundl beforemin_list(l) returnsx after
1[4, 2, 9, 2]2[2]
2[4, 9, 2]2[2, 2]
3[4, 9]4[2, 2, 4]
4[9]9[2, 2, 4, 9]
Python
print(obvious_sort([4, 2, 9, 2]))
Output
[2, 2, 4, 9]

A side effect to know about (extra)

l.remove(mini) changes the very list that was passed in. After obvious_sort(data) the caller's data is empty, because both names refer to the same list:

Python
data = [3, 1, 2]
print(obvious_sort(data))
print(data)
Output
[1, 2, 3]
[]

If the original must survive, sort a copy: write l = l[:] as the first line of obvious_sort, or call obvious_sort(data[:]).

Python
data = [3, 1, 2]
print(obvious_sort(data[:]))
print(data)
Output
[1, 2, 3]
[3, 1, 2]

5.2Matrix multiplication using functions

The same matrix multiplication as in Week 4, broken into small, reusable helper functions: one to build a zero matrix, one for a dot product, one to pull out a row, one to pull out a column, and one that ties them all together.

Python
def initialize_mat(dim):
    C = []
    for i in range(dim):
        C.append([])
    for i in range(dim):
        for j in range(dim):
            C[i].append(0)
    return C

def dot_product(u, v):
    dim = len(u)
    ans = 0
    for i in range(dim):
        ans = ans + (u[i] * v[i])
    return ans

def row(M, i):
    dim = len(M)
    l = []
    for k in range(dim):
        l.append(M[i][k])
    return l

def column(M, j):
    dim = len(M)
    l = []
    for k in range(dim):
        l.append(M[k][j])
    return l

def mat_mul(A, B):
    dim = len(A)
    C = initialize_mat(dim)
    for i in range(dim):
        for j in range(dim):
            C[i][j] = dot_product(row(A, i), column(B, j))
    return C
mat_mul is wired from small helpersmat_mul(A, B)initialize_matzerosrow(A, i)row icolumn(B, j)column jdot_productu · veach helper does exactly one jobsmall pieces are easy to test and reuse
Figure 8. mat_mul is wired from small helpers
FunctionJobTakesReturns
initialize_mat(dim)builds a dim × dim matrix of zerosdima list of lists
dot_product(u, v)sum of u[i] * v[i]two listsa number
row(M, i)the i-th row of MM, ia list
column(M, j)the j-th column of MM, ja list
mat_mul(A, B)ties the helpers togetherA, Bthe product matrix

Each helper does exactly one job: row() and column() pull out a slice, dot_product() combines two slices, and mat_mul() just wires them together. This is the same triple-nested-loop logic from Week 4, split into readable pieces.

Python
A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
B = [[1, 2, 1], [6, 2, 3], [4, 2, 1]]
print(mat_mul(A, B))
Output
[[25, 12, 10], [58, 30, 25], [91, 48, 40]]

The answer is the same as the loop version of Week 4. Each helper can also be tried on its own (extra):

Python
A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
B = [[1, 2, 1], [6, 2, 3], [4, 2, 1]]
print(row(A, 1))
print(column(B, 0))
print(dot_product(row(A, 1), column(B, 0)))
print(initialize_mat(2))
Output
[4, 5, 6]
[1, 6, 4]
58
[[0, 0], [0, 0]]
One limit (extra). row, column and mat_mul take dim = len(M) and use it for both the rows and the columns, so they work for square matrices (same number of rows and columns) only.

5.3Going deeper

  • Why split? (extra) Each piece can be tested alone, reused elsewhere, and replaced with a better version (for example a faster min_list) without touching the rest.
  • Cost (extra). The function version does the same number of multiplications as the loop version; the benefit is clarity, not speed.

5.4Exam tips and common mistakes

Exam tips
  • Draw the call picture: who calls whom, what is passed, what comes back.
  • Trace the sorting by hand with two columns: what is left in l, what has gone into x.
Common mistakes
  • Expecting the list you passed to obvious_sort to still be there afterwards.
  • Mixing up row(B, j) and column(B, j) in mat_mul.
  • Forgetting return in a helper, so mat_mul works with None.

Practice questions (graded-assignment style)

Q1Sorted?

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def min_list(l):
    mini = l[0]
    for v in l:
        if v < mini:
            mini = v
    return mini
def obvious_sort(l):
    x = []
    while len(l) > 0:
        m = min_list(l)
        x.append(m)
        l.remove(m)
    return x
data = [3, 1, 2]
print(obvious_sort(data))
print(data)
1
[1, 2, 3]
2
[3, 2, 1]
3
[1, 2, 3] then []
4
[] then [1, 2, 3]
Solution
Idea

l.remove empties the passed list.

Python
def min_list(l):
    mini = l[0]
    for v in l:
        if v < mini:
            mini = v
    return mini
def obvious_sort(l):
    x = []
    while len(l) > 0:
        m = min_list(l)
        x.append(m)
        l.remove(m)
    return x
data = [3, 1, 2]
print(obvious_sort(data))
print(data)
Output
[1, 2, 3]
[]
  • The function returns the sorted list [1, 2, 3], and the original data has been emptied.
  • Answer: option 3.
Q2A dot product

Numerical Answer Type (NAT).

What does the program print?

Python
def dot_product(u, v):
    ans = 0
    for i in range(len(u)):
        ans = ans + (u[i] * v[i])
    return ans
print(dot_product([1, 2, 3], [4, 5, 6]))
Solution
Idea

Multiply matching items and add.

Python
def dot_product(u, v):
    ans = 0
    for i in range(len(u)):
        ans = ans + (u[i] * v[i])
    return ans
print(dot_product([1, 2, 3], [4, 5, 6]))
Output
32
  • 1·4 + 2·5 + 3·6 = 32.
  • Answer: 32
Q3A column

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def column(M, j):
    l = []
    for k in range(len(M)):
        l.append(M[k][j])
    return l
print(column([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 1))
1
[1, 2, 3]
2
[1, 4, 7]
3
[3, 6, 9]
4
[2, 5, 8]
Solution
Idea

column(M, j) takes item j of every row.

Python
def column(M, j):
    l = []
    for k in range(len(M)):
        l.append(M[k][j])
    return l
print(column([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 1))
Output
[2, 5, 8]
  • Column index 1 is the second column: 2, 5, 8.
  • Answer: option 4.
Q4Matrix cell

Numerical Answer Type (NAT).

What is the value printed?

Python
A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
B = [[1, 2, 1], [6, 2, 3], [4, 2, 1]]
print(sum(A[2][k] * B[k][1] for k in range(3)))
Solution
Idea

Row 2 of A against column 1 of B.

Python
A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
B = [[1, 2, 1], [6, 2, 3], [4, 2, 1]]
print(sum(A[2][k] * B[k][1] for k in range(3)))
Output
48
  • 7·2 + 8·2 + 9·2 = 48.
  • Answer: 48
Q5Which fix?

Multiple Choice Question (MCQ): choose the one correct option.

How can obvious_sort(data) be changed to leave data unchanged?

1
Call obvious_sort(data[:]) so it sorts a copy
2
Remove the return statement
3
Use global data
4
Rename x to data
Solution
Idea

Sort a copy of the list.

  • data[:] is a separate list, so the remove calls empty the copy, not data.
  • Answer: option 1.
Q6Modular facts

Multiple Select Question (MSQ): select all that apply.

Choose the correct statements.

1
mat_mul calls dot_product, row and column
2
Splitting into helpers makes each piece easier to test
3
The function version needs fewer multiplications than the loop version
4
row and column as written work for square matrices
Solution
Idea

The number of multiplications is unchanged.

  • Statement 3 is false: the work is the same, only the structure is clearer.
  • Answer: options 1, 2 and 4.
Revision overview
Sortingmin_list finds the smallest
obvious_sort moves it to x
l.remove empties l
Matrixinitialize_mat, dot_product
row, column
mat_mul ties them
Result[[25, 12, 10], [58, 30, 25], [91, 48, 40]]
same as Week 4
ModulesChapter 5
IdeaSplit a big problem
Solve the pieces
Wire them together
CautionSide effect on the passed list
Square matrices only
BenefitReadable, testable, reusable
Revision: sorting and matrices with functions on one page
Next · RecursionRecursion
06
Recursion

Recursion

Functions that call themselves: factorial, Fibonacci, base conditions and limits.

Recursion is the process of defining something in terms of itself. A physical-world example is two parallel mirrors facing each other: any object placed between them is reflected again and again.

6.1What is recursion?

In Python, a function can call other functions. It is even possible for a function to call itself. These constructs are termed recursive functions.

Python
def recurse():
    # ...
    recurse()
    # ...
recurse()
Analogy. Russian dolls: to count the dolls, open the biggest one, and you find a smaller doll that needs the very same counting. You keep opening until you reach the smallest doll that does not open. Recursion is that: the same job on a smaller piece, until the smallest piece can be answered directly.

6.2Example: factorial

The factorial of n is n(n-1)(n-2)...3*2*1. It is defined in terms of a smaller factorial: n! = n × (n-1)!.

Python
def factorial(x):
    if x == 1:
        return 1
    else:
        return (x * factorial(x - 1))
num = 3
print("The factorial of", num, "is", factorial(num))
Output
The factorial of 3 is 6

factorial() is a recursive function as it calls itself. When called with a positive integer, it recursively calls itself with a smaller number. Each call multiplies the number with the factorial of the number below it, until it is equal to 1. The steps:

Python
factorial(3)              # 1st call with 3
3 * factorial(2)          # 2nd call with 2
3 * 2 * factorial(1)      # 3rd call with 1
3 * 2 * 1                 # returns from 3rd call as 1
3 * 2                     # returns from 2nd call
6                         # returns from 1st call
factorial(3): the calls go down, then the answers come back upfactorial(3)3 * factorial(2)3 * 2 * factorial(1)3 * 2 * 1↓calls go down until the base case↑ factorial(1) returns 1↑ factorial(2) = 2 * 1 = 2↑ factorial(3) = 3 * 2 = 6answers come back upeach call waits for the one below it
Figure 9. factorial(3): the calls go down, then the answers come back up
The base condition

Our recursion ends when the number reduces to 1. This is called the base condition. Every recursive function must have a base condition that stops the recursion, or else the function calls itself infinitely.

The depth limit. The Python interpreter limits the depth of recursion to help avoid infinite recursion, which would result in a stack overflow. By default the maximum depth of recursion is 1000. If the limit is crossed it results in a RecursionError.

Python
import sys
print(sys.getrecursionlimit())
def f(n):
    return f(n + 1)
f(0)
Error
1000
RecursionError: maximum recursion depth exceeded
A bug in the source example (extra). The base condition is x == 1, so factorial(0) (and any negative number) never reaches it: the calls go 0, -1, -2, ... forever until the limit is crossed. A safer base condition is x <= 1, which also gives the correct 0! = 1.
Python
def factorial(x):
    if x == 1:
        return 1
    else:
        return (x * factorial(x - 1))
print(factorial(0))
Error
RecursionError: maximum recursion depth exceeded
Python
def factorial(x):
    if x <= 1:
        return 1
    return x * factorial(x - 1)
print(factorial(0), factorial(1), factorial(5))
Output
1 1 120

6.3More recursive examples

Sum of the first n numbers

Python
def sum_n(n):
    if n == 0:
        return 0
    return n + sum_n(n - 1)
print(sum_n(10))
Output
55

The smaller problem is sum_n(n - 1); the base case is sum_n(0) = 0. The chain: sum_n(3) = 3 + sum_n(2) = 3 + 2 + sum_n(1) = 3 + 2 + 1 + sum_n(0) = 6.

Fibonacci numbers

Python
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)
print(fib(10))
print([fib(i) for i in range(10)])
Output
55
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Each Fibonacci number is the sum of the two before it: fib(n) = fib(n - 1) + fib(n - 2), with the two base cases fib(0) = 0 and fib(1) = 1 (the test n <= 1 covers both). Here the function calls itself twice.

Calls made by fib(4): the same values are computed again and againfib(4)fib(3)fib(2)fib(2)fib(1)fib(1)fib(0)fib(1)fib(0)fib(2) appears twice, fib(1) three times9 calls for fib(4); the number of calls roughly doubles per step
Figure 10. Calls made by fib(4): the same values are computed again and again

Counting the calls (extra). Because the same values are recomputed, the number of calls grows very fast:

Python
calls = 0
def fib(n):
    global calls
    calls += 1
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)
fib(4)
print(calls)
calls = 0
fib(10)
print(calls)
calls = 0
fib(20)
print(calls)
Output
9
177
21891

6.4Recursion or a loop?

Python
def fact_loop(n):
    result = 1
    for i in range(2, n + 1):
        result = result * i
    return result
def fact_rec(n):
    if n <= 1:
        return 1
    return n * fact_rec(n - 1)
print(fact_loop(5), fact_rec(5))
Output
120 120

Both give the same answer. The loop is usually faster and uses less memory; the recursive version reads like the definition.

6.5Advantages and disadvantages of recursion

Advantages
  • Recursive functions make the code look clean and elegant.
  • A complex task can be broken down into simpler sub-problems using recursion.
  • Sequence generation is easier with recursion than with some nested iteration.
Disadvantages
  • Sometimes the logic behind recursion is hard to follow through.
  • Recursive calls are expensive (inefficient) as they take up a lot of memory and time.
  • Recursive functions are hard to debug.

6.6Going deeper

  • The recipe (extra). To write a recursive function: (1) find the base case that can be answered directly, (2) write the step that reduces the problem, (3) check that every call moves closer to the base case.
  • Why the depth limit helps (extra). Each unfinished call waits in memory. Without a limit an endless recursion would eat all the memory; RecursionError is Python stopping it safely.

6.7More recursion: gcd, power, digit sum, Fibonacci and the Tower of Hanoi

Every recursive function has the same two parts: a base case that answers directly, and a recursive step that reduces the problem to a smaller one of the same kind.

A. Four small recursive functions

Python
def power(x, n):
    if n == 0:                 # base case: x to the power 0 is 1
        return 1
    return x * power(x, n - 1)

def digit_sum(n):
    if n < 10:                 # base case: one digit
        return n
    return n % 10 + digit_sum(n // 10)

def gcd(a, b):
    if b == 0:                 # base case: gcd(a, 0) is a
        return a
    return gcd(b, a % b)       # Euclid's step

def count_down_sum(n):
    if n == 0:
        return 0
    return n + count_down_sum(n - 1)

print(power(2, 10))
print(digit_sum(9045))
print(gcd(252, 105))
print(count_down_sum(3))
Output
1024
18
21
6
FunctionBase caseRecursive step
power(x, n)n=0n = 0: 1x⋅xn−1x \cdot x^{n-1}
digit_sum(n)n<10n < 10: nnlast digit + sum of the rest (n % 10, n // 10)
gcd(a, b)b=0b = 0: aagcd⁡(b,a mod b)\gcd(b, a \bmod b)

Trace of digit_sum(9045): 5+digit_sum(904)5 + \text{digit\_sum}(904), then 4+digit_sum(90)4 + \text{digit\_sum}(90), then 0+digit_sum(9)0 + \text{digit\_sum}(9), and digit_sum(9)=9\text{digit\_sum}(9) = 9. Back up the stack: 0+9=90 + 9 = 9, 4+9=134 + 9 = 13, 5+13=185 + 13 = 18.

B. Fibonacci by recursion repeats work

fib(n)=fib(n−1)+fib(n−2)\text{fib}(n) = \text{fib}(n-1) + \text{fib}(n-2), with fib(0)=0\text{fib}(0) = 0 and fib(1)=1\text{fib}(1) = 1, is a natural recursion. But each call makes two more, and the same values are recomputed many times. Counting the calls:

Python
calls = 0
def fib(n):
    global calls
    calls += 1
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

for n in (4, 5, 6):
    calls = 0
    value = fib(n)
    print("fib(" + str(n) + ") =", value, "in", calls, "calls")
Output
fib(4) = 3 in 9 calls
fib(5) = 5 in 15 calls
fib(6) = 8 in 25 calls

The number of calls grows roughly like the Fibonacci numbers themselves: a loop that keeps the last two values does the same job with only nn steps. This is the main disadvantage of recursion to mention in an answer: repeated work and extra memory for each pending call.

C. The Tower of Hanoi

Move nn disks from peg A to peg C using peg B, never putting a larger disk on a smaller one. The recursion says: move n−1n - 1 disks to B, move the biggest disk to C, then move the n−1n - 1 disks from B onto C. So the number of moves M(n)M(n) satisfies M(n)=2M(n−1)+1M(n) = 2M(n - 1) + 1, M(0)=0M(0) = 0, giving M(n)=2n−1M(n) = 2^n - 1.

Python
def hanoi(n, a, b, c, moves):
    if n == 0:
        return
    hanoi(n - 1, a, c, b, moves)      # clear the top n-1 disks onto b
    moves.append((a, c))              # move the largest disk
    hanoi(n - 1, b, a, c, moves)      # bring the n-1 disks onto c

for n in (1, 2, 3, 4):
    moves = []
    hanoi(n, "A", "B", "C", moves)
    print(n, len(moves))
Output
1 1
2 3
3 7
4 15

D. No base case, no end

If the recursion never reaches a base case, the calls pile up until Python stops them with a RecursionError ("maximum recursion depth exceeded"):

Python
def forever(n):
    return forever(n + 1)

forever(0)
Output
RecursionError: maximum recursion depth exceeded
Checklist for a recursive function. (1) Is there a base case that returns without calling itself? (2) Does every recursive call move closer to it (smaller n, shorter string)? (3) Is the returned value of the recursive call actually used (return f(n - 1), not just f(n - 1))?

6.8Exam tips and common mistakes

Exam tips
  • Trace a recursive call by writing the chain down (3 * factorial(2), ...) and then the answers back up.
  • First name the base condition: if you cannot find one, the function never stops.
Common mistakes
  • Forgetting the base condition (infinite recursion, RecursionError).
  • A recursive step that does not move toward the base case.
  • Forgetting the return on the recursive call, which hands back None.

Practice questions (graded-assignment style)

Q1Factorial

Numerical Answer Type (NAT).

What does the program print?

Python
def factorial(x):
    if x == 1:
        return 1
    return x * factorial(x - 1)
print(factorial(4))
Solution
Idea

4 * 3 * 2 * 1.

Python
def factorial(x):
    if x == 1:
        return 1
    return x * factorial(x - 1)
print(factorial(4))
Output
24
  • factorial(4) = 4 × factorial(3) = 4 × 6 = 24.
  • Answer: 24
Q2Sum of numbers

Numerical Answer Type (NAT).

What does the program print?

Python
def sum_n(n):
    if n == 0:
        return 0
    return n + sum_n(n - 1)
print(sum_n(5))
Solution
Idea

5 + 4 + 3 + 2 + 1 + 0.

Python
def sum_n(n):
    if n == 0:
        return 0
    return n + sum_n(n - 1)
print(sum_n(5))
Output
15
  • sum_n(5) = 5+4+3+2+1+0 = 15.
  • Answer: 15
Q3Fibonacci

Numerical Answer Type (NAT).

What does the program print?

Python
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)
print(fib(6))
Solution
Idea

The sequence is 0, 1, 1, 2, 3, 5, 8.

Python
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)
print(fib(6))
Output
8
  • fib(6) is the 7th number of 0, 1, 1, 2, 3, 5, 8, which is 8.
  • Answer: 8
Q4No base case

Multiple Choice Question (MCQ): choose the one correct option.

What happens when the program runs?

Python
def f(n):
    return f(n + 1)
print(f(0))
1
It prints 0
2
It prints 1
3
RecursionError
4
It prints None
Solution
Idea

Nothing stops the calls.

Python
def f(n):
    return f(n + 1)
print(f(0))
Error
RecursionError: maximum recursion depth exceeded
  • The function always calls itself and never stops, so Python raises a RecursionError.
  • Answer: option 3.
Q5Calls of fib(4)

Numerical Answer Type (NAT).

How many calls of fib are made in all when fib(4) is evaluated, counting the first call? (See the call tree.)

Solution
Idea

Count the boxes of the call tree.

  • The tree has 1 + 2 + 4 + 2 = 9 boxes: fib(4); fib(3), fib(2); fib(2), fib(1), fib(1), fib(0); fib(1), fib(0).
  • Answer: 9
Q6Recursion facts

Multiple Select Question (MSQ): select all that apply.

Choose the correct statements.

1
Every recursive function needs a base condition
2
The default maximum recursion depth in Python is 1000
3
A recursive function is always faster than a loop
4
Crossing the depth limit gives a RecursionError
Solution
Idea

Recursive calls cost memory and time.

  • Statement 3 is false: recursive calls are usually slower than a loop.
  • Answer: options 1, 2 and 4.
Q1Trace a recursive sum

Numerical Answer Type (NAT).

What does the program print?

Python
def f(n):
    if n == 0:
        return 0
    return n % 10 + f(n // 10)

print(f(4073))
Solution
Idea

Each call adds the last digit and recurses on the number without it, until the number becomes 0.

Python
def f(n):
    if n == 0:
        return 0
    return n % 10 + f(n // 10)

print(f(4073))
Output
14
  • f(4073)=3+f(407)=3+7+f(40)=3+7+0+f(4)=3+7+0+4+f(0)f(4073) = 3 + f(407) = 3 + 7 + f(40) = 3 + 7 + 0 + f(4) = 3 + 7 + 0 + 4 + f(0), and f(0)=0f(0) = 0. Total 1414.
  • Answer: 14
Q2Recursive gcd

Numerical Answer Type (NAT).

What does the program print?

Python
def gcd(a, b):
    if b == 0:
        return a
    return gcd(b, a % b)

print(gcd(48, 18))
Solution
Idea

Follow the pair: gcd⁡(a,b)→gcd⁡(b,a mod b)\gcd(a, b) \to \gcd(b, a \bmod b) until the second number is 0.

Python
def gcd(a, b):
    if b == 0:
        return a
    return gcd(b, a % b)

print(gcd(48, 18))
Output
6
  • (48,18)→(18,12)→(12,6)→(6,0)(48, 18) \to (18, 12) \to (12, 6) \to (6, 0). The answer is the first number when the second is 0: 66.
  • Answer: 6
Q3How many calls does fib(5) make?

Numerical Answer Type (NAT).

The function fib(n) returns n if n < 2 and fib(n - 1) + fib(n - 2) otherwise. Counting the first call, how many times is fib called in total when we run fib(5)?

Solution
Idea

Let C(n)C(n) be the number of calls. Then C(0)=C(1)=1C(0) = C(1) = 1 and C(n)=1+C(n−1)+C(n−2)C(n) = 1 + C(n-1) + C(n-2).

  • C(2)=1+1+1=3C(2) = 1 + 1 + 1 = 3, C(3)=1+3+1=5C(3) = 1 + 3 + 1 = 5, C(4)=1+5+3=9C(4) = 1 + 5 + 3 = 9, C(5)=1+9+5=15C(5) = 1 + 9 + 5 = 15.
  • Running the code with a call counter confirms it (the call counts for n=4,5,6n = 4, 5, 6 are 9,15,259, 15, 25).
  • Answer: 15
Q4What goes wrong?

Multiple Choice Question (MCQ): choose the one correct option.

What happens when countdown(3) is called?

Python
def countdown(n):
    print(n)
    countdown(n - 1)
1
It prints 3 2 1 and stops.
2
It prints 3 2 1 0 and stops.
3
It keeps printing smaller and smaller numbers until a RecursionError stops it.
4
It prints only 3.
Solution
Idea

There is no base case, so nothing makes the recursion stop.

  • The numbers go 3,2,1,0,−1,−2,…3, 2, 1, 0, -1, -2, \dots with no end, until Python raises RecursionError: maximum recursion depth exceeded.
  • Adding if n == 0: return at the top (a base case) would fix it.
  • Answer: option 3.
Q5Moves in the Tower of Hanoi

Numerical Answer Type (NAT).

What is the minimum number of moves to transfer 5 disks in the Tower of Hanoi?

Solution
Idea

M(n)=2M(n−1)+1M(n) = 2M(n - 1) + 1, so M(n)=2n−1M(n) = 2^n - 1.

  • M(1)=1M(1) = 1, M(2)=3M(2) = 3, M(3)=7M(3) = 7, M(4)=15M(4) = 15, M(5)=2⋅15+1=31M(5) = 2 \cdot 15 + 1 = 31 (and 25−1=312^5 - 1 = 31).
  • Answer: 31
Q6Statements about recursion

Multiple Select Question (MSQ): select all that apply.

Choose the correct statements.

1
Every recursive function needs at least one base case.
2
A recursive function can always be rewritten with a loop.
3
The recursive Fibonacci function computes the same value only once.
4
Each pending recursive call takes memory until it returns.
Solution
Idea

Recall the advantages and disadvantages of recursion.

  • 1 ✓ without a base case the calls never stop. 2 ✓ any recursion can be replaced by a loop (sometimes with an explicit stack). 3 ✗ it recomputes the same values many times. 4 ✓ calls stack up in memory.
  • Answer: options 1, 2 and 4.
Revision overview
IdeaA function calls itself
Smaller problem each time
Mirrors facing each other
PartsBase condition stops it
Recursive step shrinks it
Calls go down, answers up
LimitDepth 1000 by default
RecursionError beyond
RecursionChapter 6
Classicsfactorial: 3! = 6
sum_n(10) = 55
fib(10) = 55
CostCalls take memory and time
fib(20) makes 21891 calls
StyleClean and elegant
Hard to debug
Revision: recursion on one page
Next · PracticeTry It Yourself
07
Practice

Try It Yourself

The ten tasks of the source, each with a worked and executed solution.

Ten tasks from the source, to be done without looking back at the examples. Each has a worked, really executed solution. Try the task first, then compare.

7.1Tasks 1 to 5: functions, return, scope, global, defaults

Example 11Task 1: a basic function

Write a function that takes a temperature in Celsius and returns it in Fahrenheit.

Solution
Python
def c_to_f(c):
    return (c * 9/5) + 32
print(c_to_f(100))
print(c_to_f(0))
print(c_to_f(37))
Output
212.0
32.0
98.6

100 °C is 212.0 °F, freezing point 0 °C is 32.0 °F, and normal body temperature 37 °C is 98.6 °F (extra).

Example 12Task 2: return vs print

Write a function that prints "Hi" instead of returning it, then explain what print(function()) shows.

Solution
Python
def say_hi():
    print("Hi")
print(say_hi())
Output
Hi
None

It prints "Hi", then None: say_hi() has no return statement.

Example 13Task 3: scope

Predict the output: a variable x = 5 outside a function, and the function sets a local x = 100 and prints it, then print x again outside.

Solution
Python
x = 5
def show():
    x = 100
    print("Inside:", x)
show()
print("Outside:", x)
Output
Inside: 100
Outside: 5
Example 14Task 4: the global keyword

Write a function that increments a global counter variable by 1 every time it is called.

Solution
Python
counter = 0
def increment():
    global counter
    counter += 1
    return counter
print(increment())
print(increment())
Output
1
2
Example 15Task 5: default arguments

Write greet(name, greeting="Hello") and call it both with and without the second argument.

Solution
Python
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"
print(greet("Asha"))
print(greet("Asha", "Welcome"))
Output
Hello, Asha!
Welcome, Asha!

7.2Tasks 6 to 10: keywords, lists, recursion, primes

Example 16Task 6: keyword arguments

Write a function that builds a full address from city, state and pin, and call it using keyword arguments in a different order.

Solution
Python
def address(city, state, pin):
    return f"{city}, {state} - {pin}"
print(address(pin="600001", city="Chennai", state="TN"))
Output
Chennai, TN - 600001
Example 17Task 7: list and function

Write a function that takes a list and returns the sum of all even numbers in it.

Solution
Python
def sum_evens(l):
    total = 0
    for n in l:
        if n % 2 == 0:
            total += n
    return total
print(sum_evens([1, 2, 3, 4, 5, 6]))
Output
12

2 + 4 + 6 = 12.

Example 18Task 8: recursion, sum of n numbers

Write a recursive function to find the sum of the first n natural numbers.

Solution
Python
def sum_n(n):
    if n == 0:
        return 0
    return n + sum_n(n - 1)
print(sum_n(10))
Output
55
Example 19Task 9: recursion, Fibonacci

Write a recursive function to compute the nth Fibonacci number.

Solution
Python
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)
print(fib(10))
Output
55
Example 20Task 10: combined, primes below 50

Write a function that checks whether a number is prime, then use it to print all primes below 50.

Solution
Python
def is_prime(n):
    if n < 2:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True
for num in range(50):
    if is_prime(num):
        print(num, end=' ')
Output
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 

There is a space after the last number because end=' ' puts one after every print. The function returns False as soon as it finds a divisor, so it does not scan the rest. Only is_prime decides; the loop just asks it for each number from 0 to 49.

Python
def is_prime(n):
    if n < 2:
        return False
    i = 2
    while i * i <= n:
        if n % i == 0:
            return False
        i += 1
    return True
print([k for k in range(50) if is_prime(k)])
Output
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]

The second version (extra) stops testing at the square root of n (i * i <= n) and gives the same primes with fewer steps.

7.3Going deeper

  • Predict first. For each task write the expected output before running. A mismatch means a misunderstanding of return, scope or the order of the calls.
  • Variations (extra). Task 1 in the reverse direction; Task 7 for odd numbers; Task 8 with a recursion for the product; Task 10 returning the list of primes instead of printing them.

7.4Exam tips and common mistakes

Exam tips
  • Read what each function must do: print something, or return something. Many marks hide in that difference.
  • Test the function on a tiny input you can do by hand.
Common mistakes
  • Printing the result inside the function when the task says "return".
  • Forgetting the base case in a recursive task.
  • Using global for something that a parameter and a return would handle better.

Practice questions (graded-assignment style)

Q1Task 1 check

Numerical Answer Type (NAT).

What does the program print?

Python
def c_to_f(c):
    return (c * 9/5) + 32
print(c_to_f(100))
Solution
Idea

100 * 9/5 + 32.

Python
def c_to_f(c):
    return (c * 9/5) + 32
print(c_to_f(100))
Output
212.0
  • 100 × 9/5 = 180.0, and 180.0 + 32 = 212.0.
  • Answer: 212.0
Q2Task 2 check

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def say_hi():
    print("Hi")
print(say_hi())
1
Hi
2
None
3
Hi then None
4
An error
Solution
Idea

The function prints and returns nothing.

Python
def say_hi():
    print("Hi")
print(say_hi())
Output
Hi
None
  • "Hi" is printed inside the function; then print shows the returned value, which is None.
  • Answer: option 3.
Q3Task 7 check

Numerical Answer Type (NAT).

What does the program print?

Python
def sum_evens(l):
    total = 0
    for n in l:
        if n % 2 == 0:
            total += n
    return total
print(sum_evens([2, 3, 4, 7, 12]))
Solution
Idea

Add only the even numbers.

Python
def sum_evens(l):
    total = 0
    for n in l:
        if n % 2 == 0:
            total += n
    return total
print(sum_evens([2, 3, 4, 7, 12]))
Output
18
  • The even numbers are 2, 4, 12: 2 + 4 + 12 = 18.
  • Answer: 18
Q4Task 10 check

Numerical Answer Type (NAT).

How many primes does the program print?

Python
def is_prime(n):
    if n < 2:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True
count = 0
for num in range(50):
    if is_prime(num):
        count += 1
print(count)
Solution
Idea

There are 15 primes below 50.

Python
def is_prime(n):
    if n < 2:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True
count = 0
for num in range(50):
    if is_prime(num):
        count += 1
print(count)
Output
15
  • The primes below 50 are 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47: fifteen of them.
  • Answer: 15
Q5Keyword call

Multiple Choice Question (MCQ): choose the one correct option.

What does the program print?

Python
def address(city, state, pin):
    return f"{city}, {state} - {pin}"
print(address(pin="600001", city="Chennai", state="TN"))
1
TN, Chennai - 600001
2
Chennai, TN - 600001
3
600001, Chennai - TN
4
An error
Solution
Idea

Keywords match names, not positions.

Python
def address(city, state, pin):
    return f"{city}, {state} - {pin}"
print(address(pin="600001", city="Chennai", state="TN"))
Output
Chennai, TN - 600001
  • The names decide the values, so the order of the call does not matter.
  • Answer: option 2.
Q6Task facts

Multiple Select Question (MSQ): select all that apply.

Choose the correct statements.

1
print(say_hi()) shows None after the greeting when say_hi only prints
2
The counter in Task 4 needs global to be changed
3
sum_n needs a base case to stop
4
is_prime returns True as soon as it finds a divisor
Solution
Idea

A divisor means the number is not prime.

  • Statement 4 is false: finding a divisor makes is_prime return False.
  • Answer: options 1, 2 and 3.
Next · SummaryWeek 5 Summary Sheet
08
Summary

Week 5 Summary Sheet

One page to revise all of Week 5: the sheet, the errors, the checklist.

Everything from Week 5 on one page: the revision sheet, the chart of arguments and scope, and the usual mistakes.

8.1Revision sheet

TopicKey point
def name(params):Defines a function
returnExits a function, optionally sending back a value
No returnFunction returns None by default
Local scopeVariables inside a function are not visible outside it
Global scopeOuter variables are readable, but not writable, inside a function
global xLets a function modify an outer variable
Default argumentdef f(a, b=10):, b is optional
RuleNon-default arguments cannot follow default ones
Keyword argumentf(b=2, a=4): order does not matter
RuleKeyword arguments must follow positional ones
Function typesIn-built, library, string methods, user-defined
RecursionA function calling itself
Base conditionStops the recursion from running forever
Max recursion depth1000 by default; crossing it gives a RecursionError

8.2Errors you can now read

What went wrongThe error
too few argumentsTypeError: add() missing 1 required positional argument: 'b'
too many argumentsTypeError: add() takes 2 positional arguments but 3 were given
a default before a non-defaultSyntaxError: parameter without a default follows parameter with a default
positional after keywordSyntaxError: positional argument follows keyword argument
assigning to an outer name without globalUnboundLocalError: cannot access local variable 'x' where it is not associated with a value
recursion with no base caseRecursionError: maximum recursion depth exceeded

8.3Last-minute checklist

  • Function syntax: def, parameters, colon, body
  • Calling a function with the right number of arguments
  • return vs a function with no return (returns None)
  • Local scope vs global scope
  • The global keyword and when it is needed
  • Default arguments and their ordering rule
  • Keyword arguments and their ordering rule
  • Mixing positional and keyword arguments correctly
  • In-built vs library vs user-defined functions
  • Writing small helper functions (min, max, average of a list)
  • Sorting a list using a helper "find minimum" function
  • Recursion: a function calling itself
  • Identifying the base condition in a recursive function
  • Factorial and Fibonacci as classic recursion examples

8.4Common errors to avoid

MistakeCorrect idea
print(sums)Shows the function object. Call it: print(sums(5, 6)).
using print and expecting a value backprint shows, return gives back.
assigning to an outside variableMakes a new local, unless the name is declared global.
def f(a=1, b):Non-default after default is a SyntaxError.
f(a=1, 2)Positional after keyword is a SyntaxError.
recursion without a base caseNever stops: RecursionError.
factorial(0) with x == 1Never reaches the base case. Use x <= 1.
Revision overview
Functionsdef, parameters, body
return or None
Calls jump in and back
ScopeLocal inside
Global outside, read only
global x to modify
ArgumentsDefault: defaults last
Keyword: after positional
Count must match
Week 5Functions
Exampleslist_min, list_max, average
obvious_sort with min_list
mat_mul from helpers
RecursionBase condition + smaller step
factorial, sum_n, fib
Depth limit 1000
Avoidprint(f) is not a call
Missing base case
Local vs global confusion
Revision: all of Week 5 on one page
Back to top