DSM
70 XP
30 minsIntermediate70 XP

Higher-Order Functions

You've been sneaking up on a big idea for three lessons: sorted(key=len) passed a function, the timed() wrapper received one, .apply() will take one per column. Time to say it plainly — in Python, functions are values. Store them, pass them, return them. Code that operates on code.

What you'll learn

  • Pass functions as arguments — by name, without calling them
  • Use map() and filter(), and know when a comprehension is clearer
  • Write functions that RETURN configured functions (factories)
  • Store functions in data structures and dispatch on them
  • Recognize the higher-order shapes inside pandas and sklearn APIs

What

A higher-order function either takes a function as an argument (map, filter, sorted), returns a function as its result (a 'factory'), or both. Everything else is ordinary Python — functions are objects like any other value.

Why

Higher-order thinking is how data tooling is built: pandas' .apply and .agg take your functions; sklearn pipelines chain them; decorators wrap them. Understand functions-as-values and these stop being magic APIs and become plain patterns you could write yourself.

Where it's used

map/filter over records, .apply()/.agg() in pandas, validator registries, function factories for configurable transforms, and (later) decorators.

Where this runs in production

pandas.agg takes your functions

df.groupby('region').agg(total=('sales', sum), top=('sales', max)) — aggregation IS higher-order: you hand pandas the functions to run per group.

DjangoURL routing tables

A Django site maps URL patterns to view FUNCTIONS stored in a list — dispatch is 'look up the function, call it', the registry pattern at web scale.

RayDistributed map

Ray parallelizes work by shipping your function to worker processes over a cluster — map() semantics stretched across machines, same mental model.

Theory

The core ideas, in plain language.

The foundation: a def creates a function OBJECT and binds a name to it — exactly like x = 5 binds a name to an int. The name without parentheses IS the object (pass it, store it, alias it); parentheses are the separate act of calling. Everything in this lesson follows from that one distinction.
def shout(text):
    return text.upper() + '!'

announce = shout          # alias — no call
print(announce('deploy')) # DEPLOY!
print(shout is announce)  # True — same object, two names

announce isn't a copy; it's a second sticky note on the same function (the Variables lesson's whiteboard, paying off). This is what you did with key=len — handed sorted the len object itself.

Analogy: The contractor and the specialist

A general contractor doesn't lay tile — they're hired WITH a specialist in hand: 'renovate this kitchen, use THIS tiler'. map() is such a contractor: it handles walking the rooms (iteration) while the function you hand it does the craft (transformation). Factories go one step further: a workshop that doesn't tile either — it TRAINS specialist tilers to order ('make me a tiler who only does 10cm blue tiles') and sends them out to work.

Key Concept
map and filter — and their comprehension twins

map(func, items) applies func to every item; filter(func, items) keeps items where func returns True. Both return lazy iterators — wrap in list() to see results. Honest Python style: comprehensions usually read better when you'd write a fresh lambda ([x*2 for x in xs] beats map(lambda x: x*2, xs)), while map/filter shine when the function ALREADY EXISTS: map(str.strip, lines), filter(is_valid, rows).

lines = ['  alpha ', 'beta  ', '  gamma']
clean = list(map(str.strip, lines))
print(clean)

scores = [72, 45, 91, 58]
passing = list(filter(lambda s: s >= 60, scores))
print(passing)

map with an existing function: no lambda, pure signal. filter with a fresh predicate: fine, though [s for s in scores if s >= 60] is equally good — know both, choose the readable one.

Key Concept
Function factories: functions that return functions

A factory is a def containing another def, returning the inner one. The inner function REMEMBERS the factory's arguments (the mechanics of that memory — closures — is next lesson's whole topic; today we use it). make_multiplier(3) returns a function that triples; make_validator(0, 120) returns an age-checker. One factory, a family of configured tools.

Functions also live happily inside data structures. A list of cleaning steps run in order; a registry mapping a record type to its handler. This turns 'which function should run?' from an if/elif chain into a data lookup — add a handler by adding an entry, touching no logic.
Watch out
The two classic slips

One: calling too early — map(clean(), rows) passes clean's RESULT (probably a crash or a string); map(clean, rows) passes clean itself. If you see 'TypeError: 'str' object is not callable', you called too early somewhere. Two: forgetting map/filter are lazy — print(map(...)) shows '<map object ...>', not data; list() it, or loop it, to realize the work.

Visual Learning

See the concept, then explore it.

Three higher-order shapes

Take a function, return a function, store functions — click each to see the pattern and where you'll meet it.

Worked Examples

Watch it built up, one line at a time.

Very EasyPass a function by name

Apply an existing transform to a whole list with map.

Step 1 of 1

to_kilometers — no parentheses — hands map the function itself. map calls it once per element; list() collects the lazy results.

Code
01def to_kilometers(miles):
02 return round(miles * 1.60934, 1)
03 
04distances = [3.0, 26.2, 0.5]
05print(list(map(to_kilometers, distances)))
Output
[4.8, 42.2, 0.8]
Practice Coding

Your turn — write the code.

Your task

An online store needs tiered discount functions. Write a factory make_discount(pct) returning a function that applies that percentage to a price (rounded to 2 decimals). Create student (10%) and vip (25%) discounters, then use map to apply the vip discount to a whole cart.

Expected output
180.0
150.0
[37.49, 90.0, 11.62]

Write your solution in the editor on the right, then hit Run.

Exercises

Prove it. Reach 80% to complete the lesson.

Mastery Gate0% / 80% required
Easy0/2 solved

What makes a function 'higher-order'?

What does print(map(str.upper, ['a', 'b'])) show?

Medium0/3 solved

Which call is broken? def clean(s): return s.strip() rows = [' a ', ' b ']

ScenarioA parsing module has grown an if/elif chain: if fmt == 'csv': parse_csv(f) elif fmt == 'json': parse_json(f) elif fmt == 'xml': ... — now a fourth format is coming, and the chain is duplicated in two places.

What's the higher-order refactor?

Using filter and a named predicate is_round(n) (True when n is divisible by 10), keep the round numbers from [95, 100, 42, 30, 7, 60] and print them. Expected output: [100, 30, 60]

Named predicate:is_round is defined with def and passed by name
Realized output:The filter iterator is wrapped in list() before printing
Hard0/2 solved

Write a factory make_suffixer(suffix) returning a function that appends suffix to a string. Build add_csv = make_suffixer('.csv'), then use map to convert ['sales', 'users'] into filenames. Print the list. Expected output: ['sales.csv', 'users.csv']

Factory returns a function:make_suffixer returns the inner def, not a string
Composed with map:The configured function is passed to map by name

def make_adder(n): def add(x): return x + n return add add5 = make_adder(5) add9 = make_adder(9) print(add5(1), add9(1)) What prints?

Complete 80% more exercises to unlock.
Interview Prep

How this shows up in real interviews.

What does 'functions are first-class values' mean in Python, and why does it matter for data work?

Show model answer

It means a function is an ordinary object: created by def, bound to a name, and usable anywhere a value is — assigned to other names, stored in lists and dicts, passed as arguments, returned from other functions. The name alone references the object; parentheses perform the separate act of calling. It matters for data work because the entire tooling surface assumes it: sorted(key=...), df['col'].apply(func), groupby().agg(func), sklearn pipelines of transformers, and decorators all receive YOUR function as a value and control when to call it. Once you see the APIs as 'they take my function', you can also invert it — write your own map-like utilities, registries, and pipelines instead of if/elif sprawl.

map/filter versus list comprehensions — how do you choose?

Show model answer

They're expressively equivalent for the common cases, so the choice is readability. My rule: when the function already exists, map/filter is cleaner — map(str.strip, lines) and filter(is_valid, rows) name the operation with zero ceremony. When I'd have to write a fresh lambda, the comprehension usually wins — [x * 2 for x in xs] over map(lambda x: x * 2, xs), and comprehensions handle transform-plus-filter in one expression where map-inside-filter nests awkwardly. Two technical notes worth adding: map and filter are lazy iterators — nothing runs until consumed, which is a feature for large streams and a gotcha when you print one expecting data — and at pandas scale the real answer becomes vectorized column operations, with .apply as the fallback.

Describe the function-factory pattern and a real situation where you'd use it.

Show model answer

A factory is a function that builds and returns another function, configured by the factory's arguments: def make_range_check(low, high) defines an inner check(value) using low and high, and returns it. Each factory call produces an independent function remembering its own configuration — the remembering is a closure over the factory's variables. Real uses: generating per-column validators from a config file (loop the config, build one checker per column, store them in a dict keyed by column name); building parameterized transforms for a cleaning pipeline; and rate-limiters or retry policies configured per external service. The pattern's payoff is that configuration becomes a VALUE you can pass around, test, and store — and it's the exact mechanism decorators are built from, which makes it a favorite interview stepping-stone question.

Common Mistakes to Avoid

1) Calling instead of passing: map(clean(), rows) runs clean immediately — drop the parentheses. 2) Printing a map/filter object and thinking it's broken — they're lazy; list() them. 3) Wrapping existing functions in lambdas (lambda s: s.strip() → just str.strip). 4) Factories that return inner() instead of inner — returning the CALL's result, not the function. 5) Registries without a missing-key plan — HANDLERS[kind] crashes on unknowns; .get() + explicit handling reports them. 6) Consuming a lazy iterator twice — the second pass is empty; realize to a list if you need it twice.

Ask the AI Tutor

Try these prompts in the AI Tutor panel: • 'Quiz me: which of these six expressions pass a function and which call it?' • 'Help me refactor this if/elif chain into a dispatch dict.' • 'Show me a factory that builds validators from a config list.' • 'Trace what map actually does, step by step, on three elements.' • 'Interview mode: ask me about first-class functions and grade my answer.'

Glossary

Higher-order function — takes and/or returns functions. First-class value — anything assignable, passable, and returnable; Python functions qualify. map(func, iterable) — lazily applies func to every element. filter(pred, iterable) — lazily keeps elements where pred is True. Predicate — a function returning True/False. Factory — a function that returns a newly built, configured function. Registry / dispatch table — a dict mapping keys to handler functions. Lazy iterator — computes values on demand; consumed once. Alias — a second name bound to the same function object. Pipeline — an ordered collection of functions applied in sequence.

Recommended Resources

• Docs: the built-ins reference for map() and filter(), plus 'Functional Programming HOWTO' for the wider toolkit. • Read: sklearn's Pipeline documentation intro — recognize the list-of-steps pattern you just built by hand. • Practice: refactor any if/elif chain from your earlier code into a dispatch dict with a .get() fallback. • Next in DSM: factories work because inner functions REMEMBER outer variables — Scope & Closures explains exactly where Python looks names up, and what that memory really is.

Recap

✓ Functions are values: the name references, the parentheses call — everything follows from that. ✓ map/filter apply your function across data lazily; comprehensions are their (often clearer) twins. ✓ Prefer map/filter when the function exists; comprehensions when you'd write a lambda. ✓ Factories return configured functions — configuration as a value. ✓ Functions in dicts/lists give dispatch tables and pipelines — extension by data, not by elif. ✓ Two slips to watch: calling too early, and trusting a lazy iterator you never realized. Next up: Scope & Closures. Factories left one mystery — how does the inner function remember the factory's variables after it returns? The LEGB rule and closures are the answer.

scratchpad — preview this lesson's challenge anytime
discount_factory.pyPython
Ready
Output

Run your code to see the output here.