7.2 Lambda Expressions and Higher-Order Functions

Key Takeaways

  • The lambda keyword defines anonymous, inline function objects evaluated dynamically at runtime using the syntax lambda arguments: expression.
  • Lambda bodies are restricted to a single syntactic expression; statements (return, raise, pass, assignments, loops) cannot be placed inside a lambda.
  • map(func, *iterables) and filter(func, iterable) return lazy iterators in Python 3 that compute transformed or filtered items on demand.
  • filter(None, iterable) removes all falsy values (0, False, empty strings, None, empty collections) using Python's native truthiness evaluation.
  • sorted(), list.sort(), min(), and max() accept a unary key callable parameter to perform custom, complex, and multi-criteria comparisons.
Last updated: August 2026

Lambda Expressions and Higher-Order Functions

In Python, functions are first-class citizens—they can be assigned to variables, passed as arguments to other functions, stored in data structures, and returned as values from functions. A function that accepts another function as an argument or returns a function is called a higher-order function. To support functional paradigms without cluttering namespaces with trivial helper functions, Python provides anonymous functions via the lambda keyword.


1. Anonymous Functions and the lambda Keyword

A lambda function (or lambda form) is an inline, un-named function defined at the point of expression evaluation.

Syntax

lambda parameter1, parameter2, ...: expression
# Standard named function
def square(x):
    return x ** 2

# Equivalent anonymous lambda function assigned to a variable
sq = lambda x: x ** 2

print(square(5))  # 25
print(sq(5))      # 25
print(type(sq))   # <class 'function'>

At runtime, Python treats a function created via def and a function created via lambda as instances of the exact same class: types.FunctionType. The only significant difference is that lambda functions have their __name__ attribute set to "<lambda>" instead of an explicit identifier.


2. Argument Support and Syntactic Restrictions

While lambdas can accept any standard Python argument pattern, their internal structure is strictly limited by the grammar of the language.

Argument Varieties

Lambda expressions support all parameter passing mechanisms available to standard functions:

# No arguments
get_pi = lambda: 3.14159
print(get_pi())  # 3.14159

# Multiple positional arguments
add = lambda x, y: x + y
print(add(10, 20))  # 30

# Default parameter values
greet = lambda name, prefix="Hello": f"{prefix}, {name}!"
print(greet("Alice"))              # Hello, Alice!
print(greet("Bob", prefix="Hi"))    # Hi, Bob!

# Variable positional arguments (*args)
sum_all = lambda *args: sum(args)
print(sum_all(1, 2, 3, 4, 5))  # 15

# Keyword arguments (**kwargs)
format_kv = lambda **kwargs: ", ".join(f"{k}={v}" for k, v in kwargs.items())
print(format_kv(host="localhost", port=8080))  # host=localhost, port=8080

Rigid Syntactic Restrictions on the Lambda Body

On the PCAP exam, understanding what cannot be written inside a lambda is just as important as knowing what can:

  1. Single Expression Only: The body of a lambda must contain exactly one syntactic expression. It cannot contain a suite of multiple expressions separated by semicolons or newlines.
  2. Implicit Return: The result of evaluating the single expression is automatically returned. Writing an explicit return keyword inside a lambda raises a SyntaxError.
  3. No Statements Allowed: Statements perform actions but do not evaluate to values. The following statements are strictly forbidden inside a lambda:
    • Assignments: lambda x: x = x + 1 (SyntaxError)
    • Control flow statements: while, for, pass, break, continue (SyntaxError)
    • Exception statements: raise ValueError(), try, except (SyntaxError)
    • Assertions: assert x > 0 (SyntaxError)
  4. Conditional Expressions ARE Permitted: While an if statement is forbidden, Python's ternary conditional expression (expr1 if cond else expr2) evaluates to a single value and is fully legal:
# Legal: Ternary expression in lambda
parity = lambda n: "Even" if n % 2 == 0 else "Odd"
print(parity(7))  # Odd
print(parity(8))  # Even

3. The map() Built-in Function

The built-in map() function applies a transformation function to every item of an iterable and yields the results.

Signature and Laziness

map(function, iterable, *additional_iterables)

In Python 3, map() returns a lazy map iterator object (<class 'map'>), computing values only when requested rather than building an entire list in memory:

numbers = [1, 2, 3, 4, 5]
mapped_iter = map(lambda x: x * 10, numbers)

print(mapped_iter)        # <map object at 0x...>
print(next(mapped_iter))  # 10
print(next(mapped_iter))  # 20

# Convert remaining elements to a concrete list
print(list(mapped_iter))  # [30, 40, 50]

Multiple Iterables and the Shortest Termination Rule

If multiple iterables are supplied to map(), the mapping function must accept as many arguments as there are iterables. Iteration terminates as soon as the shortest iterable is exhausted:

list_a = [1, 2, 3, 4, 5]
list_b = [10, 20, 30]       # Length 3 (shortest)

sums = list(map(lambda x, y: x + y, list_a, list_b))
print(sums)  # [11, 22, 33] (Elements 4 and 5 of list_a are ignored)

4. The filter() Built-in Function

The built-in filter() function constructs an iterator from elements of an iterable for which a predicate function returns a truthy value.

Signature

filter(function_or_None, iterable)
values = [12, -4, 0, 25, -9, 30, -1]
positives = list(filter(lambda x: x > 0, values))
print(positives)  # [12, 25, 30]

The None Predicate Feature (Truthiness Filter)

If the first argument to filter() is None, Python uses the identity truth-testing function. It filters out all elements that evaluate to False in a boolean context (0, 0.0, "", [], {}, None, False):

mixed_data = [0, "hello", False, 42, "", None, [1, 2], {}, -5]
truthy_items = list(filter(None, mixed_data))
print(truthy_items)  # ['hello', 42, [1, 2], -5]

Comparing map() / filter() with Comprehensions

| Pattern | Higher-Order Function Pipeline | Equivalent List Comprehension | | :--- | :--- | :--- | :--- | | Transform | list(map(lambda x: x**2, seq)) | [x**2 for x in seq] | | Filter | list(filter(lambda x: x > 0, seq)) | [x for x in seq if x > 0] | | Filter + Transform | list(map(lambda x: x**2, filter(lambda x: x > 0, seq))) | [x**2 for x in seq if x > 0] |

In modern Python, list comprehensions are generally preferred for readability, but map() and filter() remain essential when working with existing named callables (e.g., map(int, string_list) or filter(str.isalpha, chars)).


5. Writing Your Own Functions That Accept Lambdas

map() and filter() are not special: they simply declare a parameter and call it. Objective PCAP-31-03 5.2 explicitly covers self-defined functions taking lambdas as arguments, and writing one requires no syntax you have not already seen — the parameter is an ordinary name that happens to hold a callable.

def apply_twice(operation, value):
    """Call `operation` on `value`, then on the result."""
    return operation(operation(value))

print(apply_twice(lambda x: x * 3, 2))      # 18  -> (2*3)*3
print(apply_twice(lambda s: s + "!", "hi")) # 'hi!!'

A second common shape is a self-defined function that builds a result under a caller-supplied rule, which is how key= parameters work internally:

def summarize(items, transform, keep):
    result = []
    for item in items:
        if keep(item):
            result.append(transform(item))
    return result

readings = [-4, 7, 0, 12, -1]
print(summarize(readings, lambda n: n * 100, lambda n: n > 0))
# [700, 1200]

Two rules govern these designs:

  • A lambda is just a function object. type(lambda x: x) is <class 'function'>, so a parameter holding a lambda can equally receive a def-defined function, a built-in such as abs, or a method reference such as str.upper.
  • Give the callable parameter a default when the operation is optional. def scale(seq, op=lambda x: x): lets callers omit the transformation entirely.

Exam Trap: apply_twice(lambda x: x * 3, 2) passes the lambda object; writing apply_twice(lambda x: x * 3(2)) instead calls 3(2) inside the lambda body and raises TypeError: 'int' object is not callable the moment the lambda runs.


6. Custom Sorting with sorted(), list.sort(), min(), and max()

Python provides powerful sorting mechanisms through the built-in sorted() function and the mutable list.sort() method.

sorted() vs list.sort()

  • sorted(iterable, key=None, reverse=False): Returns a new sorted list from any iterable; leaves the original iterable untouched.
  • list.sort(key=None, reverse=False): Modifies the list in-place and returns None.

The key Parameter and Lambdas

The key parameter accepts a unary function (a function taking one argument) that extracts or computes a comparison key for each element. The elements are sorted based on the return values of this key function.

# Sort strings by length
words = ["elephant", "cat", "hippopotamus", "dog", "dolphin"]
sorted_by_len = sorted(words, key=lambda w: len(w))
print(sorted_by_len)  # ['cat', 'dog', 'elephant', 'dolphin', 'hippopotamus']

# Sort list of tuples by second element (e.g. price)
items = [("Laptop", 1200), ("Mouse", 25), ("Monitor", 300), ("Keyboard", 75)]
sorted_by_price = sorted(items, key=lambda item: item[1])
print(sorted_by_price)
# [('Mouse', 25), ('Keyboard', 75), ('Monitor', 300), ('Laptop', 1200)]

Multi-Criteria Sorting via Tuples

When sorting requires multiple precedence rules (e.g. sort primarily by score descending, secondarily by student name ascending), the lambda can return a tuple:

students = [
    {"name": "Charlie", "score": 88},
    {"name": "Alice", "score": 95},
    {"name": "Bob", "score": 88},
    {"name": "David", "score": 95}
]

# Primary sort: score descending (-score), Secondary sort: name ascending (name)
students_sorted = sorted(students, key=lambda s: (-s["score"], s["name"]))
print(students_sorted)
# [
#   {'name': 'Alice', 'score': 95},
#   {'name': 'David', 'score': 95},
#   {'name': 'Bob', 'score': 88},
#   {'name': 'Charlie', 'score': 88}
# ]

Custom Key with min() and max()

The min() and max() built-ins also accept the key parameter to identify extreme elements based on custom criteria:

employees = [
    {"name": "Sarah", "salary": 95000},
    {"name": "John", "salary": 120000},
    {"name": "Emma", "salary": 82000}
]

top_earner = max(employees, key=lambda emp: emp["salary"])
lowest_earner = min(employees, key=lambda emp: emp["salary"])

print("Top Earner:", top_earner["name"])      # Top Earner: John
print("Lowest Earner:", lowest_earner["name"])  # Lowest Earner: Emma
Loading diagram...
Higher-Order Function Transformation and Filtering Pipeline
Test Your Knowledge

What is the output of the following Python program?

nums1 = [1, 2, 3, 4]
nums2 = [10, 20]
result = list(map(lambda a, b: a + b, nums1, nums2))
print(result)

A
B
C
D
Test Your Knowledge

What is the output of passing the following list to filter() with None as the predicate?

data = [0, 1, False, 2, '', 'Python', None, [], [99]]
result = list(filter(None, data))
print(result)

A
B
C
D
Test Your Knowledge

Which of the following lambda expressions will raise a SyntaxError during script compilation?

A
B
C
D
Test Your Knowledge

Consider the following Python code that sorts a list of tuples:

records = [('Alice', 85), ('Bob', 90), ('Charlie', 85), ('David', 90)]
sorted_records = sorted(records, key=lambda r: (-r[1], r[0]))
print(sorted_records)
What is the value of sorted_records?

A
B
C
D