Skip to main content

Python Lambda Expressions — Anonymous Functions: Practice Problems & Exercises

Practice: Lambda Expressions — Anonymous Functions at Engineering Depth

11 problems4 Easy4 Medium3 Hard40-55 min
← Back to lesson

Easy

#1Square with LambdaEasy
lambdabasics

Write a single lambda expression assigned to square that returns the square of its argument. Call it with 7.

Python
square = lambda x: x * x
print(square(7))
Solution
square = lambda x: x * x
print(square(7)) # 49

Explanation: lambda x: x * x creates an anonymous function object. Assigning it to square gives it a name, but the function's internal __name__ attribute remains '<lambda>'.

# Create a lambda that squares a number, then call it with 7
square = None  # replace with a lambda

print(square(7))
Expected Output
49
Hints

Hint 1: A lambda takes the form: lambda x: expression.

Hint 2: The expression should return x multiplied by itself.


#2Sort Strings by LengthEasy
lambdasortedkey

Use sorted() with a lambda key to sort the list words by string length, shortest first.

Python
words = ['banana', 'fig', 'apple', 'kiwi', 'strawberry']
result = sorted(words, key=lambda w: len(w))
print(result)
Solution
words = ['banana', 'fig', 'apple', 'kiwi', 'strawberry']
result = sorted(words, key=lambda w: len(w))
print(result) # ['fig', 'kiwi', 'apple', 'banana', 'strawberry']

Explanation: sorted() calls the key function once per element and sorts by the returned values. lambda w: len(w) is equivalent to str.__len__ — you could also write key=len directly, but the lambda form illustrates the pattern.

words = ['banana', 'fig', 'apple', 'kiwi', 'strawberry']

# Sort words by length (shortest first) using a lambda key
result = sorted(words, key=None)  # fix the key

print(result)
Expected Output
['fig', 'kiwi', 'apple', 'banana', 'strawberry']
Hints

Hint 1: Pass a lambda as the key= argument to sorted().

Hint 2: The lambda should receive a word and return its length.


#3Filter Even NumbersEasy
lambdafilterbasics

Use filter() with a lambda to extract all even numbers from numbers.

Python
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda n: n % 2 == 0, numbers))
print(evens)
Solution
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda n: n % 2 == 0, numbers))
print(evens) # [2, 4, 6, 8, 10]

Explanation: filter() returns a lazy iterator that yields each element for which the predicate returns a truthy value. Wrapping in list() materialises it.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Use filter() + a lambda to keep only even numbers
evens = list(filter(None, numbers))  # fix the first argument

print(evens)
Expected Output
[2, 4, 6, 8, 10]
Hints

Hint 1: filter(func, iterable) keeps elements where func(element) is truthy.

Hint 2: A number is even when number % 2 == 0.


#4Sort Tuples by Second ElementEasy
lambdasortedtuples

Sort the list of (number, fruit) tuples alphabetically by the fruit name (second element).

Python
pairs = [(1, 'banana'), (3, 'apple'), (2, 'cherry')]
result = sorted(pairs, key=lambda t: t[1])
print(result)
Solution
pairs = [(1, 'banana'), (3, 'apple'), (2, 'cherry')]
result = sorted(pairs, key=lambda t: t[1])
print(result) # [(3, 'apple'), (1, 'banana'), (2, 'cherry')]

Explanation: The lambda extracts the second element of each tuple. sorted() uses those string values for comparison, leaving the tuples intact in the output.

pairs = [(1, 'banana'), (3, 'apple'), (2, 'cherry')]

# Sort by the string (second element) alphabetically
result = sorted(pairs, key=None)  # fix the key

print(result)
Expected Output
[(3, 'apple'), (1, 'banana'), (2, 'cherry')]
Hints

Hint 1: Index into the tuple with t[1] to get the second element.

Hint 2: Lambda: lambda t: t[1]


Medium

#5Multi-Key SortMedium
lambdasortedtuple key

Sort students by grade descending, then by name ascending when grades are tied. Return a single sorted() call using a lambda key.

Python
students = [
    ('Alice', 85),
    ('Bob', 92),
    ('Charlie', 85),
    ('Diana', 92),
]
result = sorted(students, key=lambda s: (-s[1], s[0]))
print(result)
Solution
students = [
('Alice', 85),
('Bob', 92),
('Charlie', 85),
('Diana', 92),
]
result = sorted(students, key=lambda s: (-s[1], s[0]))
print(result)
# [('Bob', 92), ('Diana', 92), ('Alice', 85), ('Charlie', 85)]

Explanation: The lambda returns (-grade, name). Negating the grade makes higher grades sort first (since -92 < -85). When grades are equal, the second tuple element name breaks the tie alphabetically.

students = [
  ('Alice', 85),
  ('Bob', 92),
  ('Charlie', 85),
  ('Diana', 92),
]

# Sort: highest grade first, then alphabetically by name within same grade
result = sorted(students, key=None)  # fix the key

print(result)
Expected Output
[('Bob', 92), ('Diana', 92), ('Alice', 85), ('Charlie', 85)]
Hints

Hint 1: Return a tuple from the lambda — Python compares tuples element by element.

Hint 2: Negate the grade to sort it descending: -grade.

Hint 3: The name part sorts ascending (alphabetically) naturally.


#6Immediately Invoked LambdaMedium
lambdaIIFEcall-time evaluation

Write an immediately-invoked lambda expression (IIFE) that receives the list [1, 2, 3, 4, 5] and returns the sum of its squares. No helper variables — one expression.

Python
result = (lambda nums: sum(x * x for x in nums))([1, 2, 3, 4, 5])
print(result)
Solution
result = (lambda nums: sum(x * x for x in nums))([1, 2, 3, 4, 5])
print(result) # 55

Explanation: (lambda nums: ...)([1,2,3,4,5]) creates the lambda object and calls it in the same expression. The body uses a generator expression inside sum() — no intermediate list is allocated.

# Use an immediately-invoked lambda to compute the
# sum of squares of [1, 2, 3, 4, 5] — no separate variable assignment.
# Expected result: 55
result = None  # replace with an IIFE lambda expression

print(result)
Expected Output
55
Hints

Hint 1: Wrap the lambda in parentheses and call it immediately: (lambda ...: ...)(...)

Hint 2: sum() with a generator expression can compute sum of squares.

Hint 3: Pass the list directly as the argument to the lambda.


#7Fix the Loop-Closure TrapMedium
lambdaclosure traplate bindingdefault argument fix

The list comprehension below creates five lambdas that are all broken due to late binding — they all use i=4. Fix the lambda so each captures its own i at creation time.

Python
# Broken version
funcs_broken = [lambda x: x + i for i in range(5)]
print([f(0) for f in funcs_broken])  # [4, 4, 4, 4, 4]

# Fixed version
funcs_fixed = [lambda x, i=i: x + i for i in range(5)]
print([f(0) for f in funcs_fixed])   # [0, 1, 2, 3, 4]
Solution
funcs = [lambda x, i=i: x + i for i in range(5)]
print([f(0) for f in funcs]) # [0, 1, 2, 3, 4]

Explanation: Without i=i, every lambda's body references the name i in the enclosing scope. After the loop, i is 4, so all lambdas return x + 4. Adding i=i as a default parameter evaluates the right-hand i immediately at definition time, binding the current value into the parameter default — this is the canonical Python fix.

# This code has the classic loop-closure (late-binding) bug.
# Fix it so each lambda captures the value of i at creation time.

funcs = [lambda x: x + i for i in range(5)]

# Currently all funcs use i=4 (the final loop value)
print([f(0) for f in funcs])  # Should print [0, 1, 2, 3, 4]
Expected Output
[0, 1, 2, 3, 4]
Hints

Hint 1: Python lambdas close over the variable i, not its value at creation.

Hint 2: Force early binding by adding a default argument: lambda x, i=i: x + i

Hint 3: The default argument is evaluated at definition time, capturing the current value.


#8Compose Two Functions with LambdaMedium
lambdafunction compositionhigher-order functions

Implement compose(f, g) that returns a new function computing f(g(x)). Use a lambda inside the body.

Python
def compose(f, g):
    return lambda x: f(g(x))

double = lambda x: x * 2
add_ten = lambda x: x + 10

double_then_add = compose(double, add_ten)
add_then_double = compose(add_ten, double)

print(double_then_add(5))  # 30
print(add_then_double(5))  # 20
Solution
def compose(f, g):
return lambda x: f(g(x))

double = lambda x: x * 2
add_ten = lambda x: x + 10

print(compose(double, add_ten)(5)) # 30
print(compose(add_ten, double)(5)) # 20

Explanation: The returned lambda closes over f and g. When called with x, it applies g first, then passes the result to f. This is standard mathematical function composition: (f ∘ g)(x) = f(g(x)).

def compose(f, g):
  # Return a new function that computes f(g(x))
  pass

double = lambda x: x * 2
add_ten = lambda x: x + 10

double_then_add = compose(double, add_ten)
add_then_double = compose(add_ten, double)

print(double_then_add(5))   # double(add_ten(5)) = double(15) = 30
print(add_then_double(5))   # add_ten(double(5)) = add_ten(10) = 20
Expected Output
30\n20
Hints

Hint 1: compose(f, g) should return a callable that accepts x.

Hint 2: Use a lambda inside compose: return lambda x: f(g(x))

Hint 3: Note the order: g runs first, f runs on the result.


Hard

#9Build a Key-Value Store DispatcherHard
lambdadispatch tablehigher-order functions

Create a dispatch table ops as a plain dict where every value is a lambda. The 'div' entry must raise ValueError("division by zero") when b == 0. Implement run() to look up and call the right lambda.

Python
def _safe_div(a, b):
    if b == 0:
        raise ValueError("division by zero")
    return a / b

ops = {
    'add': lambda a, b: a + b,
    'sub': lambda a, b: a - b,
    'mul': lambda a, b: a * b,
    'div': lambda a, b: _safe_div(a, b),
}

def run(op_name, a, b):
    if op_name not in ops:
        raise KeyError(f"Unknown op: {op_name}")
    return ops[op_name](a, b)

print(run('add', 10, 3))
print(run('sub', 10, 3))
print(run('mul', 10, 3))
print(run('div', 10, 2))
Solution
def _safe_div(a, b):
if b == 0:
raise ValueError("division by zero")
return a / b

ops = {
'add': lambda a, b: a + b,
'sub': lambda a, b: a - b,
'mul': lambda a, b: a * b,
'div': lambda a, b: _safe_div(a, b),
}

def run(op_name, a, b):
if op_name not in ops:
raise KeyError(f"Unknown op: {op_name}")
return ops[op_name](a, b)

print(run('add', 10, 3)) # 13
print(run('sub', 10, 3)) # 7
print(run('mul', 10, 3)) # 30
print(run('div', 10, 2)) # 5.0

Explanation: Lambdas cannot contain statements (like if/raise), so the error-checking logic lives in a helper _safe_div. The lambda for 'div' delegates to it. This pattern — a dispatch table of lambdas/functions indexed by string keys — replaces large if/elif chains and is O(1) lookup.

# Build a mini command dispatcher using a dict of lambdas.
# Supported commands: 'add', 'sub', 'mul', 'div'
# Each maps to a lambda(a, b) that performs the operation.
# 'div' should raise ValueError if b == 0.

ops = {
  # fill in the four lambdas
}

def run(op_name, a, b):
  if op_name not in ops:
      raise KeyError(f"Unknown op: {op_name}")
  return ops[op_name](a, b)

print(run('add', 10, 3))   # 13
print(run('sub', 10, 3))   # 7
print(run('mul', 10, 3))   # 30
print(run('div', 10, 2))   # 5.0
Expected Output
13\n7\n30\n5.0
Hints

Hint 1: Each dict value is a lambda that accepts two arguments.

Hint 2: For 'div', you need to check the second argument inside the lambda body — but lambdas can only hold an expression, not a statement.

Hint 3: Use a ternary expression: value_if_true if condition else raise_alternative — or call a helper.

Hint 4: One clean approach: wrap the division in a small def, or raise inside a nested function call.


#10Memoize with a Lambda and a DictHard
lambdamemoizationclosuredefault mutable argument

Write a memoized Fibonacci lambda in a single expression (no def, no functools.lru_cache). The memoisation dict must be bound at definition time using the default-argument trick.

Python
fib = lambda n, cache={0: 0, 1: 1}: (
    cache[n] if n in cache
    else cache.setdefault(n, fib(n - 1) + fib(n - 2))
)

print(fib(0))   # 0
print(fib(1))   # 1
print(fib(10))  # 55
print(fib(30))  # 832040
Solution
fib = lambda n, cache={0: 0, 1: 1}: (
cache[n] if n in cache
else cache.setdefault(n, fib(n - 1) + fib(n - 2))
)

print(fib(10)) # 55
print(fib(30)) # 832040

Explanation: The default argument cache={} is a mutable dict created once at definition time and shared across every call (the classic mutable-default-argument pattern, used intentionally here). dict.setdefault(key, value) inserts and returns the value only if the key is absent — this is the only way to do a conditional assignment in a lambda expression. The lambda calls itself by name fib, which works because fib is defined in the enclosing scope by the time any recursive call runs.

# Implement a one-liner memoized Fibonacci using only lambdas and a dict.
# The cache dict must be created once and shared across all calls.
# Hint: use the default-argument trick to bind the dict at definition time.

fib = None  # replace with a lambda (recursive is fine here)

print(fib(0))   # 0
print(fib(1))   # 1
print(fib(10))  # 55
print(fib(30))  # 832040
Expected Output
0\n1\n55\n832040
Hints

Hint 1: Use the default-mutable-argument trick: lambda n, cache={}: ...

Hint 2: Inside the lambda body, check if n is in cache; if so return cache[n].

Hint 3: You cannot use assignment statements inside a lambda — use dict.setdefault() or the walrus operator.

Hint 4: Recursion: the lambda calls itself by name (fib) — make sure fib is assigned before calling fib(10).


#11Pipeline of TransformationsHard
lambdapipelinereducehigher-order functions

Implement pipeline(funcs, value) using functools.reduce and a lambda. It should apply each function in funcs left-to-right, threading the result through each step.

Python
from functools import reduce

def pipeline(funcs, value):
    return reduce(lambda acc, f: f(acc), funcs, value)

steps = [
    lambda x: x * 2,
    lambda x: x + 100,
    lambda x: str(x),
    lambda x: x + '!',
]

print(pipeline(steps, 10))  # 120!
print(pipeline(steps, 50))  # 200!
Solution
from functools import reduce

def pipeline(funcs, value):
return reduce(lambda acc, f: f(acc), funcs, value)

steps = [
lambda x: x * 2,
lambda x: x + 100,
lambda x: str(x),
lambda x: x + '!',
]

print(pipeline(steps, 10)) # 120!
print(pipeline(steps, 50)) # 200!

Explanation: reduce(lambda acc, f: f(acc), funcs, value) starts with acc = value, then for each function f in funcs it sets acc = f(acc). This is the functional "left fold" pattern — the accumulator carries the intermediate result and each step transforms it. The pattern is the backbone of data transformation pipelines in functional programming.

from functools import reduce

# Build a pipeline that applies a list of lambdas in sequence to a value.
# pipeline([f1, f2, f3], x) should return f3(f2(f1(x))).

def pipeline(funcs, value):
  pass

steps = [
  lambda x: x * 2,        # step 1: double
  lambda x: x + 100,      # step 2: add 100
  lambda x: str(x),       # step 3: convert to string
  lambda x: x + '!',      # step 4: append exclamation
]

print(pipeline(steps, 10))   # '120!'
print(pipeline(steps, 50))   # '200!'
Expected Output
120!\n200!
Hints

Hint 1: reduce(func, iterable, initializer) folds a sequence into a single value.

Hint 2: Think of it as: start with value, apply f1, feed result to f2, feed result to f3, ...

Hint 3: The reducing function takes the accumulated value and the next function, then applies it.

Hint 4: reduce(lambda acc, f: f(acc), funcs, value)

© 2026 EngineersOfAI. All rights reserved.