Lambda & Higher-Order Functions

0/4 in this phase0/54 across the roadmap

📖 Concept

Lambda functions are anonymous, single-expression functions. They're useful for short callbacks and key functions but should not replace named functions for complex logic.

lambda arguments: expression

Higher-order functions are functions that take other functions as arguments or return functions. Python has several built-in higher-order functions:

Function Purpose Example
map(func, iterable) Apply func to each item map(str, [1,2,3])
filter(func, iterable) Keep items where func returns True filter(bool, [0,1,"",3])
sorted(iterable, key=func) Sort using func for comparison sorted(words, key=len)
min/max(iterable, key=func) Find min/max using func max(users, key=lambda u: u.age)
reduce(func, iterable) Cumulative operation reduce(add, [1,2,3,4])

Important: map() and filter() return lazy iterators (not lists). They only compute values when iterated.

List comprehensions vs map/filter: In most cases, list comprehensions are preferred over map() and filter() in Python because they're more readable and Pythonic. Use map()/filter() when you already have a named function to apply.

functools module provides powerful higher-order function utilities:

  • functools.reduce() — cumulative binary operations
  • functools.partial() — freeze some function arguments
  • functools.lru_cache() — memoization decorator
  • functools.singledispatch() — function overloading by type

💻 Code Example

codeTap to expand ⛶
1# ============================================================
2# Lambda functions
3# ============================================================
4# Basic lambda
5square = lambda x: x ** 2
6print(square(5)) # 25
7
8# Multiple arguments
9add = lambda a, b: a + b
10print(add(3, 4)) # 7
11
12# With default arguments
13greet = lambda name, greeting="Hello": f"{greeting}, {name}!"
14print(greet("Alice")) # "Hello, Alice!"
15print(greet("Bob", "Hey")) # "Hey, Bob!"
16
17# Conditional expression in lambda
18classify = lambda x: "positive" if x > 0 else "negative" if x < 0 else "zero"
19print(classify(5)) # "positive"
20print(classify(-3)) # "negative"
21print(classify(0)) # "zero"
22
23# ============================================================
24# map() — apply function to every item
25# ============================================================
26numbers = [1, 2, 3, 4, 5]
27
28# With lambda
29doubled = list(map(lambda x: x * 2, numbers))
30print(doubled) # [2, 4, 6, 8, 10]
31
32# With named function
33def celsius_to_fahrenheit(c):
34 return c * 9/5 + 32
35
36temps_c = [0, 20, 37, 100]
37temps_f = list(map(celsius_to_fahrenheit, temps_c))
38print(temps_f) # [32.0, 68.0, 98.6, 212.0]
39
40# map with multiple iterables
41a = [1, 2, 3]
42b = [10, 20, 30]
43sums = list(map(lambda x, y: x + y, a, b))
44print(sums) # [11, 22, 33]
45
46# Equivalent list comprehension (preferred for simple cases)
47doubled_comp = [x * 2 for x in numbers]
48
49# ============================================================
50# filter() — keep items matching a condition
51# ============================================================
52numbers = range(-5, 6)
53
54# Keep positive numbers
55positives = list(filter(lambda x: x > 0, numbers))
56print(positives) # [1, 2, 3, 4, 5]
57
58# Filter with None removes falsy values
59mixed = [0, 1, "", "hello", None, False, True, [], [1, 2]]
60truthy = list(filter(None, mixed))
61print(truthy) # [1, 'hello', True, [1, 2]]
62
63# Equivalent comprehension (preferred)
64positives_comp = [x for x in numbers if x > 0]
65
66# ============================================================
67# sorted() with key functions
68# ============================================================
69words = ["banana", "apple", "cherry", "date"]
70
71# Sort by length
72by_length = sorted(words, key=len)
73print(by_length) # ['date', 'apple', 'banana', 'cherry']
74
75# Sort by last character
76by_last_char = sorted(words, key=lambda w: w[-1])
77print(by_last_char) # ['banana', 'apple', 'date', 'cherry']
78
79# Sort complex objects
80users = [
81 {"name": "Charlie", "age": 35},
82 {"name": "Alice", "age": 30},
83 {"name": "Bob", "age": 25},
84]
85
86by_age = sorted(users, key=lambda u: u["age"])
87by_name = sorted(users, key=lambda u: u["name"])
88print([u["name"] for u in by_age]) # ['Bob', 'Alice', 'Charlie']
89
90# Multi-key sorting
91from operator import itemgetter
92data = [("Alice", 30), ("Bob", 25), ("Alice", 25)]
93sorted_data = sorted(data, key=itemgetter(0, 1)) # Sort by name, then age
94print(sorted_data) # [('Alice', 25), ('Alice', 30), ('Bob', 25)]
95
96# ============================================================
97# reduce() — cumulative operation
98# ============================================================
99from functools import reduce
100
101# Sum (though sum() built-in is better for this)
102total = reduce(lambda acc, x: acc + x, [1, 2, 3, 4, 5])
103print(total) # 15
104
105# Product
106product = reduce(lambda acc, x: acc * x, [1, 2, 3, 4, 5])
107print(product) # 120
108
109# Flatten nested lists
110nested = [[1, 2], [3, 4], [5, 6]]
111flat = reduce(lambda acc, lst: acc + lst, nested, [])
112print(flat) # [1, 2, 3, 4, 5, 6]
113
114# Build a dict from pairs
115pairs = [("a", 1), ("b", 2), ("c", 3)]
116d = reduce(lambda acc, pair: {**acc, pair[0]: pair[1]}, pairs, {})
117print(d) # {'a': 1, 'b': 2, 'c': 3}
118
119# ============================================================
120# functools.partial — freeze some arguments
121# ============================================================
122from functools import partial
123
124def power(base, exponent):
125 return base ** exponent
126
127square = partial(power, exponent=2)
128cube = partial(power, exponent=3)
129
130print(square(5)) # 25
131print(cube(3)) # 27
132
133# Practical: configure a function
134import json
135pretty_json = partial(json.dumps, indent=2, sort_keys=True)
136print(pretty_json({"b": 2, "a": 1}))
137
138# ============================================================
139# operator module — named versions of operators
140# ============================================================
141from operator import add, mul, itemgetter, attrgetter
142
143# Use instead of lambdas for simple operations
144total = reduce(add, [1, 2, 3, 4, 5]) # Better than lambda a,b: a+b
145product = reduce(mul, [1, 2, 3, 4, 5])
146
147# itemgetter for dict/tuple access
148get_name = itemgetter("name")
149users = [{"name": "Charlie"}, {"name": "Alice"}, {"name": "Bob"}]
150sorted_users = sorted(users, key=get_name)
151
152# attrgetter for object attribute access
153from collections import namedtuple
154Person = namedtuple("Person", ["name", "age"])
155people = [Person("Charlie", 35), Person("Alice", 30), Person("Bob", 25)]
156sorted_people = sorted(people, key=attrgetter("age"))
157print([p.name for p in sorted_people]) # ['Bob', 'Alice', 'Charlie']

🏋️ Practice Exercise

Exercises:

  1. Use map(), filter(), and reduce() to: take a list of strings, filter out empty ones, convert to uppercase, and concatenate with commas.

  2. Sort a list of dictionaries by multiple keys: first by "department" (ascending), then by "salary" (descending). Show both lambda and operator.itemgetter approaches.

  3. Implement my_map(func, iterable) and my_filter(func, iterable) using only reduce.

  4. Use functools.partial to create a family of string formatting functions: format_currency, format_percentage, format_date.

  5. Compare performance: write the same operation using a for loop, list comprehension, map() with lambda, and map() with named function. Time each approach for a large list.

  6. Rewrite a complex reduce operation as a simple for loop. Argue which version is more readable and why.

⚠️ Common Mistakes

  • Overusing lambda for complex logic. If a lambda needs multiple operations or is hard to read, use a named def function instead. Lambdas are for simple, one-expression operations.

  • Forgetting that map() and filter() return iterators, not lists. Use list(map(...)) if you need a list. Iterators can only be consumed once.

  • Using map(lambda x: ..., items) when a list comprehension [... for x in items] would be clearer. Prefer comprehensions for simple transformations.

  • Not knowing about operator.itemgetter and operator.attrgetter — these are faster and more readable alternatives to lambda for accessing attributes and items.

  • Using reduce() for operations that have simpler alternatives: sum() for addition, math.prod() for multiplication, ''.join() for string concatenation, any()/all() for boolean reduction.

💼 Interview Questions

🎤 Mock Interview

Practice a live interview for Lambda & Higher-Order Functions