Skip to main content

Python map, filter, reduce — Lazy: Practice Problems & Exercises

Practice: map, filter, reduce — Lazy Iteration and the Pipeline Model

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

Easy

#1Double Every ElementEasy
mapbasics

Use map() with a lambda to produce a new list where each element is doubled.

Python
numbers = [1, 2, 3, 4, 5]
result = list(map(lambda x: x * 2, numbers))
print(result)
Solution
numbers = [1, 2, 3, 4, 5]
result = list(map(lambda x: x * 2, numbers))
print(result) # [2, 4, 6, 8, 10]

Explanation: map() returns a lazy map object — no computation happens until iteration. list() forces evaluation. The lambda is applied once per element; Python never allocates an intermediate list.

numbers = [1, 2, 3, 4, 5]

# Use map() to double every element
result = list(map(None, numbers))  # fix the first argument

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

Hint 1: map(func, iterable) applies func to every element lazily.

Hint 2: Pass a lambda that multiplies by 2 as the first argument.


#2Remove Empty StringsEasy
filterbasics

Use filter() to remove all empty strings from tokens. The single-argument filter(None, ...) form works perfectly here.

Python
tokens = ['hello', '', 'world', '', 'python', '']
result = list(filter(None, tokens))
print(result)
Solution
tokens = ['hello', '', 'world', '', 'python', '']
result = list(filter(None, tokens))
print(result) # ['hello', 'world', 'python']

Explanation: When None is passed as the predicate, filter uses the element itself as the truth value. Empty strings are falsy, so they are removed. This works for any falsy value: 0, None, [], {}.

tokens = ['hello', '', 'world', '', 'python', '']

# Use filter() to remove empty strings
result = list(filter(None, tokens))

print(result)
Expected Output
['hello', 'world', 'python']
Hints

Hint 1: filter(None, iterable) removes all falsy values — empty strings are falsy in Python.

Hint 2: This is the shorthand form; no lambda needed when filtering out falsy values.


#3Sum a List with reduceEasy
reducebasicsfunctools

Use functools.reduce() with a lambda to compute the sum of numbers without calling sum().

Python
from functools import reduce

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
total = reduce(lambda acc, x: acc + x, numbers)
print(total)
Solution
from functools import reduce

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
total = reduce(lambda acc, x: acc + x, numbers)
print(total) # 55

Explanation: reduce starts with the first element as the initial accumulator, then applies the lambda to (acc, next) for each subsequent element. This is the left-fold: ((((1+2)+3)+4)+...+10). For large lists, sum() is faster because it is implemented in C, but reduce generalises to any binary operation.

from functools import reduce

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

# Use reduce() to sum the list (no built-in sum())
total = reduce(None, numbers)  # fix the first argument

print(total)
Expected Output
55
Hints

Hint 1: reduce(func, iterable) folds the list left-to-right into a single value.

Hint 2: The accumulator function receives (accumulated_value, next_element).

Hint 3: Use a lambda that adds two numbers.


#4Map Over Two ListsEasy
mapmultiple iterables

Use map() with a two-argument lambda to produce the element-wise product of xs and ys.

Python
xs = [1, 2, 3, 4]
ys = [10, 20, 30, 40]
result = list(map(lambda x, y: x * y, xs, ys))
print(result)
Solution
xs = [1, 2, 3, 4]
ys = [10, 20, 30, 40]
result = list(map(lambda x, y: x * y, xs, ys))
print(result) # [10, 40, 90, 160]

Explanation: map() accepts multiple iterables and zips them together, passing one element from each to the function simultaneously. It stops at the shortest iterable (like zip()).

xs = [1, 2, 3, 4]
ys = [10, 20, 30, 40]

# Use map() with two iterables to compute element-wise products
result = list(map(None, xs, ys))  # fix the call

print(result)
Expected Output
[10, 40, 90, 160]
Hints

Hint 1: map(func, iter1, iter2) passes pairs of elements to func.

Hint 2: The lambda should accept two arguments: lambda x, y: ...


Medium

#5Chain map and filterMedium
mapfilterpipelinelazy

Filter out negative numbers, then square the positives. Build the pipeline using only filter() and map(), wrapping in list() only at the end.

Python
data = [1, -2, 3, -4, 5, -6, 7, -8, 9, -10]
result = list(map(lambda x: x * x, filter(lambda x: x > 0, data)))
print(result)
Solution
data = [1, -2, 3, -4, 5, -6, 7, -8, 9, -10]
result = list(map(lambda x: x * x, filter(lambda x: x > 0, data)))
print(result) # [1, 9, 25, 49, 81]

Explanation: filter(...) produces a lazy iterator of positives. map(...) wraps it — still lazy. list() pulls from map, which pulls from filter, which pulls from data. The chain executes in a single pass with no intermediate list. This is Python's lazy pipeline model.

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

# Step 1: filter out negative numbers
# Step 2: square the remaining numbers
# Use only map() and filter() — no list comprehensions
result = list(None)  # build the pipeline here

print(result)
Expected Output
[1, 9, 25, 49, 81]
Hints

Hint 1: Build left to right: filter first, then map.

Hint 2: Neither map nor filter materialises a list — chain them lazily.

Hint 3: Wrap only the outermost call in list() to force evaluation.


#6Flatten with reduceMedium
reduceflattenaccumulator

Use reduce() to flatten nested (a list of lists) into a single flat list using list concatenation.

Python
from functools import reduce

nested = [[1, 2], [3, 4], [5, 6], [7, 8]]
flat = reduce(lambda acc, lst: acc + lst, nested)
print(flat)
Solution
from functools import reduce

nested = [[1, 2], [3, 4], [5, 6], [7, 8]]
flat = reduce(lambda acc, lst: acc + lst, nested)
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8]

Explanation: Each acc + lst creates a new list (O(n) copy). For very large inputs, prefer itertools.chain.from_iterable() which is O(1) per step. The reduce approach is clean for moderate sizes and clearly demonstrates the left-fold pattern. An explicit initial value reduce(..., nested, []) is safer when nested might be empty.

from functools import reduce

nested = [[1, 2], [3, 4], [5, 6], [7, 8]]

# Use reduce() to flatten into a single list
flat = reduce(None, nested)  # fix

print(flat)
Expected Output
[1, 2, 3, 4, 5, 6, 7, 8]
Hints

Hint 1: The accumulator starts as the first sublist.

Hint 2: Each step should concatenate the current accumulator with the next sublist.

Hint 3: List concatenation operator is +.


#7Count Occurrences with reduceMedium
reduceaccumulatordict

Use reduce() with an empty dict as the initial accumulator to count word frequencies.

Python
from functools import reduce

words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
freq = reduce(lambda acc, w: {**acc, w: acc.get(w, 0) + 1}, words, {})
print(freq)
Solution
from functools import reduce

words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
freq = reduce(lambda acc, w: {**acc, w: acc.get(w, 0) + 1}, words, {})
print(freq) # {'apple': 3, 'banana': 2, 'cherry': 1}

Explanation: {**acc, w: acc.get(w, 0) + 1} creates a new dict on every step by spreading the existing accumulator and overwriting the key w with its incremented count. This is pure (no mutation), but O(n) space per step. A mutable alternative: use a dict.setdefault trick or just use collections.Counter in production code.

from functools import reduce

words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']

# Use reduce() to build a frequency dict — no Counter, no defaultdict
freq = reduce(None, words)  # fix

print(freq)
Expected Output
{'apple': 3, 'banana': 2, 'cherry': 1}
Hints

Hint 1: The accumulator should be a dict, so provide an empty dict as the initial value.

Hint 2: Inside the lambda, you cannot use assignment — use dict.get() to read the current count.

Hint 3: Trick: you can mutate the accumulator dict inside the lambda using dict.__setitem__ or a helper.

Hint 4: Pattern: lambda acc, w: {**acc, w: acc.get(w, 0) + 1}


#8map with operator moduleMedium
mapoperatorperformance

Use map() with operator.add (no lambda) and the two-iterable form of map to compute the sum of each (a, b) pair.

Python
import operator

pairs = [(1, 2), (3, 4), (5, 6), (7, 8)]
a_vals, b_vals = zip(*pairs)
sums = list(map(operator.add, a_vals, b_vals))
print(sums)
Solution
import operator

pairs = [(1, 2), (3, 4), (5, 6), (7, 8)]
a_vals, b_vals = zip(*pairs)
sums = list(map(operator.add, a_vals, b_vals))
print(sums) # [3, 7, 11, 15]

Explanation: operator.add is a C-level callable — faster than a Python lambda for large datasets because it skips the lambda call overhead. zip(*pairs) transposes the list of pairs into two tuples of first and second elements. The two-iterable map form then applies operator.add element-wise.

import operator

pairs = [(1, 2), (3, 4), (5, 6), (7, 8)]

# Use map() with operator.add (NOT a lambda) to sum each tuple
sums = list(map(None, *zip(*pairs)))  # fix — use map with operator.add

print(sums)
Expected Output
[3, 7, 11, 15]
Hints

Hint 1: operator.add(a, b) is a regular callable equivalent to a + b.

Hint 2: map(operator.add, iter1, iter2) works like the two-iterable map from problem 4.

Hint 3: Unpack pairs into two iterables using zip(*pairs).


Hard

#9Running Maximum with reduceHard
reduceaccumulatorstateful

Use reduce() to build a list of running maximums. At each position i, the value should be the maximum of all elements up to and including index i.

Python
from functools import reduce

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

running_max = reduce(
    lambda acc, x: acc + [max(acc[-1], x)] if acc else [x],
    numbers,
    []
)
print(running_max)
Solution
from functools import reduce

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

running_max = reduce(
lambda acc, x: acc + [max(acc[-1], x)] if acc else [x],
numbers,
[]
)
print(running_max)
# [3, 3, 4, 4, 5, 9, 9, 9, 9, 9, 9]

Explanation: The accumulator acc is a growing list. On each step, acc[-1] is the running maximum so far; we extend with max(acc[-1], x). The ternary handles the empty-acc edge case (first element). Note that acc + [...] creates a new list on every step — for production code, use itertools.accumulate(numbers, max) which is O(n) total.

from functools import reduce

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

# Use reduce() to produce a list of running maximums.
# running_max[i] = max(numbers[0], ..., numbers[i])
# Expected: [3, 3, 4, 4, 5, 9, 9, 9, 9, 9, 9]

running_max = reduce(None, numbers)  # fix

print(running_max)
Expected Output
[3, 3, 4, 4, 5, 9, 9, 9, 9, 9, 9]
Hints

Hint 1: The accumulator should be a list of running maximums seen so far.

Hint 2: Provide an initial value of [] for the accumulator.

Hint 3: Each step: append max(acc[-1], current) to acc — but lambdas cannot use append.

Hint 4: Use list concatenation: acc + [max(acc[-1], x)] if acc else [x]


#10Lazy Prime Filter PipelineHard
filtermaplazygeneratorperformance

Implement prime_squares(limit, n) using a lazy filter + map pipeline. Use itertools.islice to take only the first n results.

Python
import itertools

def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

def prime_squares(limit, n):
    primes = filter(is_prime, range(2, limit + 1))
    pairs = map(lambda p: (p, p * p), primes)
    return list(itertools.islice(pairs, n))

print(prime_squares(100, 5))
Solution
import itertools

def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True

def prime_squares(limit, n):
primes = filter(is_prime, range(2, limit + 1))
pairs = map(lambda p: (p, p * p), primes)
return list(itertools.islice(pairs, n))

print(prime_squares(100, 5))
# [(2, 4), (3, 9), (5, 25), (7, 49), (11, 121)]

Explanation: Every step is lazy: range, filter, map, and islice are all iterators. list() at the end pulls exactly n elements, stopping early. No intermediate list of 99 integers is ever created. This is the pipeline model: data flows element by element from source to sink in a single pass.

# Build a lazy pipeline that:
# 1. Generates integers from 2 up to limit (inclusive)
# 2. Filters to keep only primes (using filter + a primality lambda)
# 3. Maps each prime p to (p, p*p)
# 4. Returns the first n results as a list
#
# Do NOT materialise the integer range early — keep it lazy.

def is_prime(n):
  if n < 2:
      return False
  for i in range(2, int(n ** 0.5) + 1):
      if n % i == 0:
          return False
  return True

def prime_squares(limit, n):
  pass  # implement using filter() and map() lazily

print(prime_squares(100, 5))
# Expected: [(2, 4), (3, 9), (5, 25), (7, 49), (11, 121)]
Expected Output
[(2, 4), (3, 9), (5, 25), (7, 49), (11, 121)]
Hints

Hint 1: range(2, limit + 1) is already lazy — do not convert it to a list.

Hint 2: filter(is_prime, range(...)) stays lazy.

Hint 3: map(lambda p: (p, p*p), filtered) also stays lazy.

Hint 4: Use itertools.islice to take only the first n without exhausting the iterator.


#11Build compose_many with reduceHard
reducefunction compositionhigher-order functionsmap

Implement compose_many(*funcs) that returns a single callable applying all functions right-to-left. Use reduce() internally.

Python
from functools import reduce

def compose_many(*funcs):
    return reduce(lambda f, g: lambda x: f(g(x)), funcs)

steps = [
    str.strip,
    str.upper,
    lambda s: s + '!',
    lambda s: s * 2,
]

transform = compose_many(*steps)
print(transform('  hello  '))  # HELLO!HELLO!
Solution
from functools import reduce

def compose_many(*funcs):
return reduce(lambda f, g: lambda x: f(g(x)), funcs)

steps = [
str.strip,
str.upper,
lambda s: s + '!',
lambda s: s * 2,
]

transform = compose_many(*steps)
print(transform(' hello ')) # HELLO!HELLO!

Explanation: reduce(lambda f, g: lambda x: f(g(x)), funcs) folds the list of functions into a single composed callable. For [f, g, h], the fold produces lambda x: f(g_h(x)) where g_h = lambda x: g(h(x)) — i.e., right-to-left application. This is the standard functional definition of function composition. Because each reduce step creates a new lambda that closes over f and g, the whole chain is O(1) to build and executes left-to-right through the closures.

from functools import reduce

def compose_many(*funcs):
  # Return a single function that applies funcs right-to-left.
  # compose_many(f, g, h)(x) => f(g(h(x)))
  pass

# Test pipeline: strip whitespace, uppercase, add exclamation, repeat twice
steps = [
  str.strip,
  str.upper,
  lambda s: s + '!',
  lambda s: s * 2,
]

transform = compose_many(*steps)
print(transform('  hello  '))   # HELLO!HELLO!
Expected Output
HELLO!HELLO!
Hints

Hint 1: compose_many should reduce the list of functions right-to-left.

Hint 2: Mathematical composition: (f o g)(x) = f(g(x)) — g runs first.

Hint 3: reduce(lambda f, g: lambda x: f(g(x)), funcs) builds the chain.

Hint 4: Right-to-left means the last function in the list runs first on the input.

© 2026 EngineersOfAI. All rights reserved.