Lambda expressions are one-line functions that specify two things: the parameters and the return value.

lambda <parameters>: <return value>

While both lambda and def statements are related to functions, there are some differences.

lambda def
Type lambda is an expression def is a statement
Description Evaluating a lambda expression does not create or modify any variables. Lambda expressions just create new function objects. Executing a def statement will create a new function object and bind it to a variable in the current environment.
lambda x: x * x
def square(x):
    return x * x

A lambda expression by itself is not very interesting. As with any objects such as numbers, booleans, strings, we usually:

  • assign lambda to variables (foo = lambda x: x)
  • pass them in to other functions (bar(lambda x: x))
  • return them as the results of other functions (return lambda x: x)
  • return them as the results of other lambdas (lambda x: lambda y: x + y)

In the final example above, the outer lambda (lambda x) takes in a value x, and it returns another lambda (lambda y) that takes an argument y and returns x+y.

Environment Diagrams

Environment diagrams are one of the best learning tools for understanding lambda expressions because you're able to keep track of all the different names, function objects, and arguments to functions. We highly recommend drawing environment diagrams or using Python tutor if you get stuck doing the WWPD problems below. For examples of what environment diagrams should look like, try running some code in Python tutor. Here are the rules:


Note: As we saw in the lambda expression section above, lambda functions have no intrinsic name. When drawing lambda functions in environment diagrams, they are labeled with the name lambda or with the lowercase Greek letter λ. This can get confusing when there are multiple lambda functions in an environment diagram, so you can distinguish them by numbering them or by writing the line number on which they were defined.

  1. Draw the lambda function object and label it with λ, its formal parameters, and its parent frame. A function's parent frame is the frame in which the function was defined.

This is the only step. We are including this section to emphasize the fact that the difference between lambda expressions and def statements is that lambda expressions do not create any new bindings in the environment.


Question 1: WWPD: Lambda the Free

Use Ok to test your knowledge with the following "What Would Python Display?" questions:

python3 ok -q lambda -u

For all WWPD questions, type Function if you believe the answer is <function...>, Error if it errors, and Nothing if nothing is displayed. As a reminder, the following two lines of code will not display anything in the Python interpreter when executed:

>>> x = None
>>> x
>>> lambda x: x  # A lambda expression with one parameter x
<function <lambda> at ...>
>>> a = lambda x: x # Assigning the lambda function to the name a >>> a(5)
>>> (lambda: 3)() # Using a lambda expression as an operator in a call exp.
>>> b = lambda x: lambda: x # Lambdas can return other lambdas! >>> c = b(88) >>> c
<function <lambda> at ...
>>> c()
>>> d = lambda f: f(4) # They can have functions as arguments as well. >>> def square(x): ... return x * x >>> d(square)
>>> z = 3
>>> e = lambda x: lambda y: lambda: x + y + z
>>> e(0)(1)()
>>> f = lambda z: x + z >>> f(3)
NameError: name 'x' is not defined
>>> higher_order_lambda = lambda f: lambda x: f(x)
>>> g = lambda x: x * x
>>> higher_order_lambda(2)(g)  # Which argument belongs to which function call?
>>> higher_order_lambda(g)(2)
>>> call_thrice = lambda f: lambda x: f(f(f(x))) >>> call_thrice(lambda y: y + 1)(0)
>>> print_lambda = lambda z: print(z) # When is the return expression of a lambda expression executed? >>> print_lambda
>>> one_thousand = print_lambda(1000)
>>> one_thousand
# print_lambda returned None, so nothing gets displayed

Environment Diagram Practice

There is no submission for this component. However, we still encourage you to do these problems on paper to develop familiarity with Environment Diagrams, which will appear on the exam.

Question 2: Make Adder

Draw the environment diagram for the following code:

n = 9
def make_adder(n):
    return lambda k: k + n
add_ten = make_adder(n+1)
result = add_ten(n)

There are 3 frames total (including the Global frame). In addition, consider the following questions:

  1. In the Global frame, the name add_ten points to a function object. What is the intrinsic name of that function object, and what frame is its parent?
  2. In frame f2, what name is the frame labeled with (add_ten or λ)? Which frame is the parent of f2?
  3. What value is the variable result bound to in the Global frame?

You can try out the environment diagram at To see the environment diagram for this question, click here.

  1. The intrinsic name of the function object that add_ten points to is λ (specifically, the lambda whose parameter is k). The parent frame of this lambda is f1.
  2. f2 is labeled with the name λ the parent frame of f2 is f1, since that is where λ is defined.
  3. The variable result is bound to 19.

Question 3: Lambda the Environment Diagram

Try drawing an environment diagram for the following code and predict what Python will output.

You do not need to submit or unlock this question through Ok. Instead, you can check your work with the Online Python Tutor, but try drawing it yourself first!

>>> a = lambda x: x * 2 + 1
>>> def b(b, x):
...     return b(x + a(x))
>>> x = 3
>>> b(a, x)
21 # Interactive solution:


Question 4: Compose

Write a function that takes in 2 single-argument functions, f and g, and returns another lambda function that takes in a single argument x. The returned function should return the output of applying f(g(x)).

Hint: The staff solution is only 1 line!

def compose(f, g):
    """Write a function that takes in 2 single-argument functions, f and g, and returns another lambda function 
    that takes in a single argument x. The returned function should return the output of applying f(g(x)). 
    Hint: The staff solution is only 1 line!

    Return the composition function which given x, computes f(g(x)). 

    >>> add_two = lambda x: x + 2  		# adds 2 to x
    >>> square = lambda x: x ** 2 		# squares x
    >>> a = compose(square, add_two) 	# (x + 2 ) ^ 2
    >>> a(5) 
    >>> mul_ten = lambda x: x * 10 		# multiplies 10 with x
    >>> b = compose(mul_ten, a) 		# ((x + 2 ) ^ 2) * 10
    >>> b(5)
    >>> b(2)
"*** YOUR CODE HERE ***"
return lambda x: f(g(x))

Use OK to test your code:

python3 ok -q compose

Question 5: Mul_by_num

Using a lambda expression, complete the mul_by_num function. This function should take an argument and return a one argument function that multiplies any value passed to it by the original number. Its body must be one line long:

def mul_by_num(num):
    Returns a function that takes one argument and returns num
    times that argument.
    >>> x = mul_by_num(5)
    >>> y = mul_by_num(2)
    >>> x(3)
    >>> y(-4)
"*** YOUR CODE HERE ***"
return lambda num2: num * num2

Use OK to test your code:

python3 ok -q mul_by_num


A recursive function is a function that calls itself in its body, either directly or indirectly. Recursive functions have three important components:

  1. Base case(s), the simplest possible form of the problem you're trying to solve.
  2. Consider a recursive call on a smaller argument.
  3. Recursive case(s), where the function calls itself with a simpler argument as part of the computation.

Let's look at the canonical example, factorial:

def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n - 1)

We know by its definition that 0! is 1. So we choose n = 0 as our base case. The recursive step also follows from the definition of factorial, i.e. n! = n * (n-1)!.

The next few questions in lab will have you writing recursive functions. Here are some general tips:

  • Consider how you can solve the current problem using the solution to a simpler version of the problem. Remember to trust the recursion: assume that your solution to the simpler problem works correctly without worrying about how.
  • Think about what the answer would be in the simplest possible case(s). These will be your base cases - the stopping points for your recursive calls. Make sure to consider the possibility that you're missing base cases (this is a common way recursive solutions fail).
  • It may help to write the iterative version first.

Question 6: AB Plus C

Implement ab_plus_c, a function that takes arguments a, b, and c and computes a * b + c. You can assume a and b are both nonnegative integers. However, you can't use the * operator. Try recursion!

def ab_plus_c(a, b, c):
    """Computes a * b + c.

    >>> ab_plus_c(2, 4, 3)  # 2 * 4 + 3
    >>> ab_plus_c(0, 3, 2)  # 0 * 3 + 2
    >>> ab_plus_c(3, 0, 2)  # 3 * 0 + 2
"*** YOUR CODE HERE ***"
if b == 0: return c return a + ab_plus_c(a, b - 1, c)

Use OK to test your code:

python3 ok -q ab_plus_c

Question 7: is_palindrome

Write a recursive solution for the function is_palindrome such that it takes in any list and returns True if that list is the same way going forwards and backwards.

def is_palindrome(lst):
    """ Returns True if the list is a palindrome. A palindrome is a list 
    that reads the same forwards as backwards
    >>> is_palindrome([1, 2, 3, 4, 5])
    >>> is_palindrome(["p", "a", "l", "i", "n", "d", "r", "o", "m", "e"])
    >>> is_palindrome([True, False, True])
    >>> is_palindrome([])
    >>> is_palindrome(["a", "l", "a", "s", "k", "a"])
    >>> is_palindrome(["r", "a", "d", "a", "r"])
    >>> is_palindrome(["f", "o", "o", "l", "p", "r", "o", "o", "f"])
    >>> is_palindrome(["a", "v", "a"])
    >>> is_palindrome(["racecar", "racecar"])
    >>> is_palindrome(["r", "a", "c", "e", "c", "a", "r"])
"*** YOUR CODE HERE ***"
if len(lst) == 0 or len(lst) == 1: return True if lst[0] != lst[-1]: return False return is_palindrome(lst[1:-1])

Use OK to test your code:

python3 ok -q is_palindrome


Make sure to submit this assignment by running:

python3 ok --submit

Extra Credit Practice Open in a new window

These questions are new this semester. They're a mix of Parsons Problems, Code Tracing questions, and Code Writing questions.

Confused about how to use the tool? Check out for some problems designed to demonstrate how to solve these types of problems.

These cover some similar material to lab, so can be helpful to further review or try to learn the material. Unlike lab and homework, after you've worked for long enough and tested your code enough times on any of these questions, you'll have the option to view an instructor solution. You'll unlock each question one at a time, either by correctly answering the previous question or by viewing an instructor solution.

Starting from lab 2 onward, each set of questions are worth half (0.5) a point per lab, for a total opportunity of 4-5 points throughout the semester.

Use OK to test your code:

python3 ok -q extra_credit


Make sure to submit this assignment by running:

python3 ok --submit

Extra Questions

Extra questions are not worth extra credit and are entirely optional. They are designed to challenge you to think creatively!

Question 8: Funception

Write a function (funception) that takes in another function func_a and a number start and returns a function (func_b) that will have one parameter to take in the stop value. func_b should take the following into consideration the following in order:

  1. Takes in the stop value.
  2. If the value of start is less than 0, it should exit the function.
  3. If the value of start is greater than stop, apply func_a on start and return the result.
  4. If not, apply func_a on all the numbers from start (inclusive) up to stop (exclusive) and return the product.
def funception(func_a, start):
    """ Takes in a function (function A) and a start value.
    Returns a function (function B) that will find the product of 
    function A applied to the range of numbers from 
    start (inclusive) to stop (exclusive)

    >>> def func_a(num):
    ...     return num + 1
    >>> func_b1 = funception(func_a, 3)
    >>> func_b1(2)
    >>> func_b2 = funception(func_a, -2)
    >>> func_b2(-3)
    >>> func_b3 = funception(func_a, -1)
    >>> func_b3(4)
    >>> func_b4 = funception(func_a, 0)
    >>> func_b4(3)
    >>> func_b5 = funception(func_a, 1)
    >>> func_b5(4)
"*** YOUR CODE HERE ***"
def func_b(stop): i = start product = 1 if start < 0: return None if start > stop: return func_a(start) while i < stop: product *= func_a(i) i += 1 return product return func_b

Use OK to test your code:

python3 ok -q funception

Question 9: Towers of Hanoi

A classic puzzle called the Towers of Hanoi is a game that consists of three rods, and a number of disks of different sizes which can slide onto any rod. The puzzle starts with n disks in a neat stack in ascending order of size on a start rod, the smallest at the top, forming a conical shape.

Towers of Hanoi

The objective of the puzzle is to move the entire stack to an end rod, obeying the following rules:

  • Only one disk may be moved at a time.
  • Each move consists of taking the top (smallest) disk from one of the rods and sliding it onto another rod, on top of the other disks that may already be present on that rod.
  • No disk may be placed on top of a smaller disk.

Complete the definition of move_stack, which prints out the steps required to move n disks from the start rod to the end rod without violating the rules.

def print_move(origin, destination):
    """Print instructions to move a disk."""
    print("Move the top disk from rod", origin, "to rod", destination)

def move_stack(n, start, end):
    """Print the moves required to move n disks on the start pole to the end
    pole without violating the rules of Towers of Hanoi.

    n -- number of disks
    start -- a pole position, either 1, 2, or 3
    end -- a pole position, either 1, 2, or 3

    There are exactly three poles, and start and end must be different. Assume
    that the start pole has at least n disks of increasing size, and the end
    pole is either empty or has a top disk larger than the top n start disks.

    >>> move_stack(1, 1, 3)
    Move the top disk from rod 1 to rod 3
    >>> move_stack(2, 1, 3)
    Move the top disk from rod 1 to rod 2
    Move the top disk from rod 1 to rod 3
    Move the top disk from rod 2 to rod 3
    >>> move_stack(3, 1, 3)
    Move the top disk from rod 1 to rod 3
    Move the top disk from rod 1 to rod 2
    Move the top disk from rod 3 to rod 2
    Move the top disk from rod 1 to rod 3
    Move the top disk from rod 2 to rod 1
    Move the top disk from rod 2 to rod 3
    Move the top disk from rod 1 to rod 3
    assert 1 <= start <= 3 and 1 <= end <= 3 and start != end, "Bad start/end"
"*** YOUR CODE HERE ***"
if n == 1: print_move(start, end) else: other = 6 - start - end move_stack(n-1, start, other) print_move(start, end) move_stack(n-1, other, end)

Use OK to test your code:

python3 ok -q move_stack