Advanced

Functional Programming

Write concise code with lambdas, map, filter, and comprehensions.

What you will see

10 15

About 2 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

Functional style turns functions into data I can flow through a pipeline. I map a transformation across items, keep only the ones a predicate selects, fold a sequence down with reduce, and close over a value to build new functions, watching how each tool transforms data without mutating it.

1

map applies a function to every item and gives me a new list. I square each number with a lambda, then map the plain len function over words, printing both results to see one item in, one out.

nums = [1, 2, 3] squared = list(map(lambda x: x ** 2, nums)) print(squared) words = ["cat", "lion", "elephant"] lengths = list(map(len, words)) print(lengths)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
[1, 4, 9] [3, 4, 8]
map returns a lazy iterator, so wrap it in list() to see all the results at once.
2

filter keeps only the items a predicate accepts. I keep the even numbers, then map them through a multiply, printing each step to see how selection narrows a sequence before the next transform.

nums = [1, 2, 3, 4] even = list(filter(lambda x: x % 2 == 0, nums)) print(even) times_ten = list(map(lambda x: x * 10, even)) print(times_ten)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
[2, 4] [20, 40]
filter only passes items where the predicate is truthy. Odd numbers never make it to the next step.
3

A comprehension does the same work as map and filter in one readable expression. I square every number, then keep only the evens, printing each to see how compact it is compared to the function-pipeline way.

nums = [1, 2, 3, 4] print([x ** 2 for x in nums]) print([x for x in nums if x % 2 == 0])

Before you run it, say out loud what it will print. Then open the answer.

What it prints
[1, 4, 9, 16] [2, 4]
The 'if' part filters; anything before it transforms. Both fit on one line, which is usually clearer than map/filter/lambda.
4

reduce folds a sequence into a single value. I write the sum out the long way with a loop, then let reduce do it in one call, printing both to confirm they land on the same total.

nums = [1, 2, 3, 4] total = 0 for n in nums: total += n print(total) from functools import reduce print(reduce(lambda a, b: a + b, nums))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
10 10
reduce needs an accumulator and a starting point. Without it, the first two items seed the fold.
5

A closure captures a value from its enclosing scope, letting me build functions on the fly. I make a multiplier, use it to double and triple a number, and print both to see how one function makes many.

def multiplier(n): return lambda x: x * n double = multiplier(2) triple = multiplier(3) print(double(5)) print(triple(5))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
10 15
The inner lambda remembers n even after multiplier() has returned. Each call fixes its own n.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

Functions are values that flow through map/filter/reduce, transforming data without mutating it.
1 Define a function (often a lambda)
↓
2 map applies it to each item -> new sequence
↓
3 filter keeps matching items
↓
4 reduce accumulates into a single value
Line by line

Read the first example one line at a time. A line that prints shows what appears on screen; the rest set things up quietly.

nums = [1, 2, 3] sets nothing on screen
squared = list(map(lambda x: x ** 2, nums)) sets nothing on screen
print(squared) [1, 4, 9]
words = ["cat", "lion", "elephant"] sets nothing on screen
lengths = list(map(len, words)) sets nothing on screen
print(lengths) [3, 4, 8]
Why this works
Pure functions and side effects

Functional style favours pure functions: same input always yields the same output with no hidden state changes. Avoid mutating arguments or globals. Pure functions are easier to test, reason about, and parallelise.

Closures and currying

A closure captures variables from its enclosing scope:

def multiplier(n):
    return lambda x: x * n

double = multiplier(2)
print(double(5))   # 10

This enables currying, fixing some arguments to create a new function.

Examples

Lambda and map

Transform sequences

nums = [1, 2, 3]
squared = list(map(lambda x: x ** 2, nums))  # [1,4,9]

map applies a function to every item. A lambda is a tiny anonymous function.

Filter and reduce

Select and fold

from functools import reduce
nums = [1, 2, 3, 4]
even = list(filter(lambda x: x % 2 == 0, nums))  # [2,4]
total = reduce(lambda a, b: a + b, nums)          # 10

filter keeps items where the predicate is true; reduce folds a sequence into one value.

Comprehensions vs map/filter

Readable alternatives

nums = [1, 2, 3, 4]
squared = [x ** 2 for x in nums]
even = [x for x in nums if x % 2 == 0]

Comprehensions are usually more readable than map/filter with lambdas and can include filtering in one expression.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Data Structures Deep Dive
Previous Lesson
Data Structures Deep Dive
Next Lesson
Object-Oriented Programming
Object-Oriented Programming →