Intermediate Python 📂 Built-n Function · 1 of 1 43 min read

Python Built-in Functions

Master Python's functional toolkit — lambda, map, filter, reduce, zip, enumerate, sorted, any, all, min, max, and sum — with a clear visual diagram showing exactly how map, filter, and reduce reshape data differently. Learn when to reach for each, when a list comprehension is cleaner, and when to use tuple keys for multi-level sorting. Includes performance benchmarks, four real-world examples, and 7 golden rules.

Section 01

The Story That Explains Functional Tools

The Three Stations on a Factory Line
Imagine a conveyor belt in a fruit-packing factory. Apples roll in from one end. Along the belt sit three stations, each doing exactly one job.

Station 1 — the Painter. Every apple gets a wax coat. One apple in, one apple out. Same count, transformed. That's map().

Station 2 — the Inspector. Bruised apples get tossed. Good ones pass through. Fewer apples come out than went in. That's filter().

Station 3 — the Weigher. All remaining apples are dropped onto a single scale. Many apples go in, one total weight comes out. That's reduce().

And the tiny handwritten notes on each station — "wax it," "keep if unbruised," "add to running total" — those are lambdas. Small anonymous functions that tell each station exactly what to do.

Python's functional tools — lambda, map, filter, reduce, zip, enumerate, and friends — are the vocabulary for expressing "do this to every item", "keep only these", and "combine into one" without writing explicit loops. Learn them once and your code shrinks by half.

💡
The Core Insight

Every functional built-in follows one pattern: a small function + an iterable. The built-in handles the looping; the small function (usually a lambda) says what to do at each step. You describe the transformation, not the plumbing.


Section 02

Lambda — The Anonymous Function

Before map and filter can shine, you need lambda — Python's way of writing a one-line function without giving it a name. Think of it as a disposable function you write inline and throw away.

The Two Sides of the Same Coin

# The traditional way — named function
def square(x):
    return x * x

# The lambda way — anonymous, one-liner
square_lambda = lambda x: x * x

print(square(5))         # 25
print(square_lambda(5))  # 25 — identical result
OUTPUT
25 25
🧠 The Lambda Blueprint
Keyword
lambda — announces the function
Args
Parameters — comma-separated, before the colon: lambda x, y
Colon
The : separator — what comes after IS the return value
Body
Single expression — no statements, no if/else blocks, no loops

Lambda With Multiple Arguments

add       = lambda x, y: x + y
distance  = lambda x1, y1, x2, y2: ((x2-x1)**2 + (y2-y1)**2) ** 0.5
greet     = lambda name, greeting="Hello": f"{greeting}, {name}!"

print(add(3, 4))
print(distance(0, 0, 3, 4))
print(greet("Alice"))
print(greet("Bob", "Hey"))
OUTPUT
7 5.0 Hello, Alice! Hey, Bob!

Lambda With a Ternary — Conditional Return

# You CAN'T write if/else statements — but ternary IS an expression
grade = lambda score: "pass" if score >= 50 else "fail"

print(grade(75))   # pass
print(grade(32))   # fail
OUTPUT
pass fail
⚠️
The Golden Rule of Lambda

A lambda is for a single expression, nothing more. No statements. No return. No for loops. No try/except. If you find yourself reaching for those, you need a real def function. Assigning a lambda to a variable (square = lambda x: x*x) also usually means you should have written a proper function instead.


Section 03

Visual Diagram — Map vs Filter vs Reduce

These three functions look similar in syntax but do fundamentally different things to the data. Once you can picture the shape of each, you'll never confuse them again.

The Three Shapes of Functional Transformation
MAP Same count, transformed 1 2 3 4 x * 10 10 20 30 40 4 in → 4 out FILTER Fewer items, unchanged 1 2 3 4 x % 2 == 0 2 4 4 in → 2 out REDUCE Many items → one value 1 2 3 4 acc + x 10 4 in → 1 out

MAP transforms 1-to-1. FILTER selects a subset. REDUCE collapses everything into a single value. Same input [1, 2, 3, 4], three completely different shapes of output.


Section 04

map() — Transform Every Item

map(function, iterable) applies function to every element of iterable, returning a lazy iterator. Use list() to materialize the results.

# Traditional way: for loop with append
nums = [1, 2, 3, 4, 5]
squared = []
for n in nums:
    squared.append(n ** 2)

# map + lambda way — one line
squared = list(map(lambda n: n ** 2, nums))
print(squared)
OUTPUT
[1, 4, 9, 16, 25]

Map With a Named Function

# Any function works — not just lambdas
words = ["apple", "BANANA", "Cherry"]
lower = list(map(str.lower, words))
lengths = list(map(len, words))

print(lower)
print(lengths)
OUTPUT
['apple', 'banana', 'cherry'] [5, 6, 6]

Map With Multiple Iterables

# The lambda takes as many args as you pass iterables
prices    = [10, 20, 30]
quantities = [2, 3, 1]

totals = list(map(lambda p, q: p * q, prices, quantities))
print(totals)  # [20, 60, 30]
OUTPUT
[20, 60, 30]
🔑
Map Returns a Lazy Iterator, Not a List

In Python 3, map() returns a map object — nothing is computed until you iterate it. This is memory-efficient for large data. Wrap in list(), tuple(), or a for loop to actually consume the results. Print the map object directly and you'll see something like <map object at 0x7f...>, not your values.


Section 05

filter() — Keep Only What Passes

filter(function, iterable) keeps only elements for which function returns True. Same lazy iterator behaviour as map.

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

# Keep only even numbers
evens = list(filter(lambda n: n % 2 == 0, nums))
print(evens)

# Keep only strings that aren't empty
raw = ["Alice", "", "Bob", None, "Charlie", ""]
clean = list(filter(None, raw))    # None as function = truthy filter
print(clean)
OUTPUT
[2, 4, 6, 8, 10] ['Alice', 'Bob', 'Charlie']
📈
The filter(None, ...) Idiom

Passing None as the filter function is a shortcut for "keep truthy items only." Empty strings, zero, None, and empty containers are all dropped. Perfect for one-line cleanup of messy data.


Section 06

reduce() — Collapse to a Single Value

reduce() lives in functools. It takes a function of two arguments and applies it cumulatively across the iterable, reducing everything to a single accumulated value.

from functools import reduce

# Sum: (((1+2)+3)+4)+5 = 15
total = reduce(lambda acc, x: acc + x, [1, 2, 3, 4, 5])

# Product: 1*2*3*4*5 = 120
product = reduce(lambda acc, x: acc * x, [1, 2, 3, 4, 5])

# Max (manual): iteratively compare
biggest = reduce(lambda a, b: a if a > b else b, [3, 7, 1, 9, 4])

print(total, product, biggest)
OUTPUT
15 120 9

Reduce With an Initial Value

# The 3rd argument is the STARTING accumulator
# Useful when the iterable might be empty or you need a non-default seed

reduce(lambda acc, x: acc + x, [], 0)      # returns 0, no error
reduce(lambda acc, x: acc + x, [1, 2, 3], 100)  # 100 + 1 + 2 + 3 = 106

# Building a dict from key-value pairs
pairs = [("a", 1), ("b", 2), ("c", 3)]
result = reduce(lambda d, kv: {**d, kv[0]: kv[1]}, pairs, {})
print(result)
OUTPUT
{'a': 1, 'b': 2, 'c': 3}
⚠️
Prefer sum(), max(), min() Over reduce() When Possible

Python has purpose-built built-ins for the most common reductions: sum(nums), max(nums), min(nums). They're faster, clearer, and don't require a lambda. Save reduce for the cases these don't cover — building a dict, chained function composition, or custom accumulation logic.


Section 07

zip() — Pair Up Iterables

zip() takes any number of iterables and yields tuples of their paired elements. It stops at the shortest input.

names  = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
ages   = [30, 25, 35]

# Pair two lists — most common use
for name, score in zip(names, scores):
    print(f"{name}: {score}")

# Any number of iterables works
for name, score, age in zip(names, scores, ages):
    print(f"{name} ({age}): scored {score}")

# Build a dict from parallel lists
report = dict(zip(names, scores))
print(report)
OUTPUT
Alice: 85 Bob: 92 Charlie: 78 Alice (30): scored 85 Bob (25): scored 92 Charlie (35): scored 78 {'Alice': 85, 'Bob': 92, 'Charlie': 78}

Unzipping With zip(*data)

# The unpacking star reverses a zip — like a matrix transpose
pairs = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]

names, scores = zip(*pairs)
print(names)   # ('Alice', 'Bob', 'Charlie')
print(scores)  # (85, 92, 78)
OUTPUT
('Alice', 'Bob', 'Charlie') (85, 92, 78)

Section 08

enumerate() — Index + Value Together

enumerate() is the anti-pattern-killer for for i in range(len(...)). It pairs each element with a counter, starting at 0 (or whatever you specify).

fruits = ["apple", "banana", "cherry"]

# The anti-pattern — DON'T do this
for i in range(len(fruits)):
    print(f"{i}: {fruits[i]}")

# The Pythonic way
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")

# Start counting from 1 (useful for user-facing lists)
for rank, name in enumerate(["Gold", "Silver", "Bronze"], start=1):
    print(f"#{rank} - {name}")
OUTPUT
0: apple 1: banana 2: cherry 0: apple 1: banana 2: cherry #1 - Gold #2 - Silver #3 - Bronze

Section 09

sorted() With a Lambda Key

sorted() can sort by any derived value using the key argument. This is where lambdas really shine — you describe the sort criterion in one line.

students = [
    {"name": "Alice",   "score": 85, "age": 22},
    {"name": "Bob",     "score": 92, "age": 20},
    {"name": "Charlie", "score": 78, "age": 25},
    {"name": "Diana",   "score": 92, "age": 23},
]

# Sort by score, high to low
by_score = sorted(students, key=lambda s: s["score"], reverse=True)

# Sort by score DESC, then age ASC (tie-breaker)
compound = sorted(students, key=lambda s: (-s["score"], s["age"]))

# Sort strings by their length
words = ["banana", "pi", "apple", "kiwi"]
by_length = sorted(words, key=len)

print("By score:  ", [s["name"] for s in by_score])
print("Compound:  ", [s["name"] for s in compound])
print("By length: ", by_length)
OUTPUT
By score: ['Bob', 'Diana', 'Alice', 'Charlie'] Compound: ['Bob', 'Diana', 'Alice', 'Charlie'] By length: ['pi', 'kiwi', 'apple', 'banana']
🔑
The Tuple-Key Trick

Returning a tuple from your key lambda gives you multi-level sorting — Python compares element-by-element. Prefix with a minus for descending numeric fields (like -s["score"]). This one trick replaces most complex "sort by X, then by Y" code.


Section 10

any() and all() — Quick Truth Tests

Two of Python's cleanest built-ins. any() returns True if at least one element is truthy. all() returns True only if every element is. Both short-circuit — they stop the moment the answer is decided.

nums = [2, 4, 6, 8, 10]

# any: is there AT LEAST ONE match?
print(any(n > 5 for n in nums))    # True — 6, 8, 10 exist
print(any(n > 100 for n in nums))  # False — none qualify

# all: does EVERY element match?
print(all(n % 2 == 0 for n in nums))  # True — all even
print(all(n > 5 for n in nums))       # False — 2 and 4 fail

# Practical: validate user input
form = {"name": "Alice", "email": "a@x.com", "age": 30}
required = ["name", "email", "age"]

if all(k in form and form[k] for k in required):
    print("Form is complete")
OUTPUT
True False True False Form is complete
Short-Circuit Evaluation Is Free Speed

any() returns True on the first truthy element and stops scanning. all() returns False on the first falsy element and stops. On a million-item iterable where the answer is at position 3, you pay for 3 checks — not a million. This is why passing a generator (not a list) is the fastest way.


Section 11

min(), max(), sum() — With Keys and Defaults

nums = [3, 7, 1, 9, 4]

# Basics
print(min(nums), max(nums), sum(nums))   # 1 9 24

# With a key function — most powerful use
words = ["apple", "kiwi", "blueberry", "fig"]
print(min(words, key=len))   # 'fig' — shortest
print(max(words, key=len))   # 'blueberry' — longest

# Find the student with the highest score
students = [
    {"name": "Alice", "score": 85},
    {"name": "Bob",   "score": 92},
    {"name": "Charlie", "score": 78},
]
top = max(students, key=lambda s: s["score"])
print(f"Top student: {top['name']} with {top['score']}")

# Sum with a start value (avoids crash on empty)
print(sum([], 0))                 # 0 — safe default
print(sum([10.5, 20.5, 30], 100))    # 161.0 — starts at 100
OUTPUT
1 9 24 fig blueberry Top student: Bob with 92 0 161.0

Section 12

Practical Real-World Examples

Example 1 — Cleaning & Normalising User Input

raw_emails = [
    "  Alice@Example.com ",
    "",
    "BOB@example.com",
    None,
    "  charlie@example.COM",
]

# Chain: filter out empty/None → strip → lowercase
clean = list(map(str.lower,
                map(str.strip,
                    filter(None, raw_emails))))

print(clean)
OUTPUT
['alice@example.com', 'bob@example.com', 'charlie@example.com']

Example 2 — Building a Leaderboard

scores = [
    ("Alice",   85),
    ("Bob",     92),
    ("Charlie", 78),
    ("Diana",   92),
    ("Eve",     65),
]

# Top 3, sorted by score desc, then alphabetically for ties
leaderboard = sorted(scores, key=lambda s: (-s[1], s[0]))[:3]

for rank, (name, score) in enumerate(leaderboard, start=1):
    print(f"#{rank}  {name:8s} {score}")
OUTPUT
#1 Bob 92 #2 Diana 92 #3 Alice 85

Example 3 — Aggregating Financial Data

from functools import reduce

transactions = [
    {"type": "credit", "amount": 500},
    {"type": "debit",  "amount": 120},
    {"type": "credit", "amount": 250},
    {"type": "debit",  "amount": 75},
    {"type": "credit", "amount": 1000},
]

# Total credits
credits = sum(map(lambda t: t["amount"],
                  filter(lambda t: t["type"] == "credit", transactions)))

# Running balance using reduce
balance = reduce(
    lambda acc, t: acc + t["amount"] if t["type"] == "credit"
                        else acc - t["amount"],
    transactions, 0
)

print(f"Total credits: £{credits}")
print(f"Net balance:   £{balance}")
OUTPUT
Total credits: £1750 Net balance: £1555

Example 4 — Merging Parallel Data

ids     = [101, 102, 103, 104]
names   = ["Alice", "Bob", "Charlie", "Diana"]
active  = [True, False, True, True]

# Build a list of dicts from three parallel lists
users = [
    {"id": i, "name": n, "active": a}
    for i, n, a in zip(ids, names, active)
]

# Keep only active users
active_users = list(filter(lambda u: u["active"], users))

for u in active_users:
    print(u)
OUTPUT
{'id': 101, 'name': 'Alice', 'active': True} {'id': 103, 'name': 'Charlie', 'active': True} {'id': 104, 'name': 'Diana', 'active': True}

Section 13

Map/Filter vs Comprehensions — Which to Use?

Every map or filter call can be rewritten as a comprehension. The community leans towards comprehensions for readability, but there are cases where the functional form is genuinely better.

📚 map / filter Style
FeatureBehaviour
Reads likeApply function to iterable
Lazy?Yes — returns iterator
Best whenPassing an existing named function
Nested logicAwkward — nesting map(map(...))
🌱 Comprehension Style
FeatureBehaviour
Reads likeEnglish sentence
Lazy?No — builds full list (use generator for lazy)
Best whenInline expression, filter + transform combined
Nested logicNatural — multiple for/if clauses

Same Task, Two Styles

nums = [1, 2, 3, 4, 5, 6]

# map + filter — the functional style
result_a = list(map(lambda n: n ** 2,
                  filter(lambda n: n % 2 == 0, nums)))

# List comprehension — the Pythonic style
result_b = [n ** 2 for n in nums if n % 2 == 0]

print(result_a)
print(result_b)   # Both: [4, 16, 36]
OUTPUT
[4, 16, 36] [4, 16, 36]
SituationRecommendation
Passing an existing named function (e.g. str.strip, len) Use map — map(str.strip, lines)
Custom inline transformation with an expression Use comprehension — [x*2 for x in nums]
Filter + transform in one pass Use comprehension — [f(x) for x in it if cond]
Multiple parallel iterables Use map — map(func, a, b, c)
Complex custom accumulator logic Use reduce — or a plain for loop for clarity
Simple sum, min, max Use sum/min/max — never reduce

Section 14

Performance — Are They Faster?

import timeit
from functools import reduce

setup = "nums = list(range(100_000))"

# Squaring 100,000 numbers — four ways
t_loop = timeit.timeit(
    "r = []\nfor n in nums: r.append(n*n)",
    setup=setup, number=100
)
t_comp = timeit.timeit(
    "r = [n*n for n in nums]",
    setup=setup, number=100
)
t_map_lambda = timeit.timeit(
    "r = list(map(lambda n: n*n, nums))",
    setup=setup, number=100
)
t_map_named = timeit.timeit(
    "r = list(map(f, nums))",
    setup=setup + "\nf = lambda n: n*n", number=100
)

print(f"For loop:            {t_loop:.3f}s")
print(f"Comprehension:       {t_comp:.3f}s")
print(f"map + inline lambda: {t_map_lambda:.3f}s")
print(f"map + named func:    {t_map_named:.3f}s")
OUTPUT
For loop: 0.612s Comprehension: 0.348s map + inline lambda: 0.725s map + named func: 0.702s
📈
The Speed Ranking

For simple transformations, list comprehensions win. The overhead of a lambda call per element makes map slower than a comprehension when the transformation is trivial. Where map pulls ahead is when you're passing a fast built-in function like str.strip, int, or len — no lambda call overhead, just a direct C-level function invocation.


Section 15

Quick Reference Table

Built-inSignatureReturnsPrimary Use
lambdalambda args: exprFunction objectAnonymous one-line functions
mapmap(func, iter)Lazy iteratorTransform each element
filterfilter(func, iter)Lazy iteratorKeep matching elements
reducereduce(func, iter, init)Single valueCustom accumulation (from functools)
zipzip(*iters)Lazy iterator of tuplesPair up parallel iterables
enumerateenumerate(iter, start=0)Lazy iterator of (i, val)Add an index to iteration
sortedsorted(iter, key=None, reverse=False)New sorted listSort with custom criterion
anyany(iter)boolAt-least-one truthy check (short-circuits)
allall(iter)boolEvery-element truthy check (short-circuits)
min / maxmin(iter, key=None, default=?)Single valueSmallest / largest — with optional key
sumsum(iter, start=0)NumberAdd up numeric elements

Section 16

Golden Rules

🔑 Functional Built-ins — Non-Negotiable Rules
1
Never assign a lambda to a variable. If a lambda needs a name, it needs a proper def. square = lambda x: x*x should be def square(x): return x*x. Lambdas are for inline, one-shot use — as arguments to map, filter, sorted, etc.
2
Prefer a comprehension over map(lambda ...). They're faster AND clearer. Reserve map for cases where you're passing an existing named functionmap(str.strip, lines) is genuinely cleaner than the comprehension equivalent.
3
Reach for sum, max, min, any, all before reduce. Purpose-built built-ins are faster, clearer, and don't need imports. Use reduce only for genuinely custom accumulation logic.
4
Remember that map and filter return iterators, not lists. In Python 3, they're lazy. If you need to iterate them twice, or index them, wrap in list(). Forgetting this leads to silent bugs when the iterator is silently exhausted.
5
Kill for i in range(len(seq)). If you need the index, use enumerate. If you need to pair two iterables, use zip. range(len(...)) is a code smell in almost every appearance.
6
Use tuple keys for multi-level sorting. sorted(data, key=lambda x: (-x.score, x.name)) — high score first, alphabetical tie-break. One line replaces most custom comparator code.
7
Pass generators, not lists, to any and all. any(is_valid(x) for x in items) stops at the first True. any([is_valid(x) for x in items]) computes everything first then checks — the list is wasted work. Same for all.
You have completed Built-n Function. View all sections →