skip to content

Higher-Order Functions

Functions that take or return other functions, giving you the map/filter/fold vocabulary plus currying and wrappers. Interviewers ask because fold is what most candidates cannot explain precisely.

on this pageshow

questions

21

If a mapping step returns a list of sessions per course, what shape does the pipeline hold before flattening?

level: juniorimportance: must knowfreq 62%

answer

  1. count the layers, not the items
  2. one output slot per input element
  3. the grouping survives the map
  4. an empty inner list still takes a slot
  5. flatten concatenates in order

basics

~20 s

A list of lists: one inner list of sessions per course, in course order, with an empty inner list wherever a course scheduled nothing. Flattening one level concatenates those inner lists into a single list of sessions.

solid answer

~40 s

Element-wise mapping preserves the shape of the outer structure: one output slot per input element. If the function you hand it returns a list of sessions, the result is a list of lists - exactly one inner list per course, in the same order, including empty inner lists for the courses that scheduled nothing. The sessions are all present, but they are still grouped one level deep, so anything that wants sessions has to open every inner list first. Flattening one level concatenates those inner lists, in order, into a single list of sessions. `bind` is just those two steps fused into one operation, which is why its result length is the sum of the inner lengths rather than the number of courses.

code

pseudocode · 13 lines
pseudocode
courses = [A, B, C]
sessionsOf(A) = [a1, a2]
sessionsOf(B) = []
sessionsOf(C) = [c1, c2]

grouped = map(courses, course -> sessionsOf(course))
// [[a1, a2], [], [c1, c2]]   outer length 3, one slot per course

flat = flatten(grouped)
// [a1, a2, c1, c2]           length 4, the sum of the inner lengths

same = bind(courses, course -> sessionsOf(course))
// [a1, a2, c1, c2]           the two steps fused into one

go deeper

for a junior

Recall the shape rule: one output slot per input element. If the function returns a list, you are holding a list of lists until something flattens it.

for a middle

Explain why the outer length is pinned to the input length, and what the empty inner list means for a course that scheduled nothing.

for a senior

Catch the shape mistake in review: a count that matches the course total when somebody asked for sessions, or a stage looping over what it assumed was flat.

for a principal

Decide when the grouping is the product. Flattening is lossy, so a flat feed usually forces every element to carry the identity of the group it came from.

## What a mapping step actually promises Element-wise mapping is **shape-preserving**. Give it a structure of *n* elements and a function `f`, and you get back a structure of *n* elements, in the same order, where slot *i* holds `f(element_i)`. That promise says nothing at all about what `f` returns - only that whatever it returns lands whole in one slot of the result. So when `f` is *give me this course's scheduled sessions*, and a course may have four sessions, one, or none, the result is not a list of sessions. It is a **list of lists of sessions**: - exactly one inner list per course, never more and never fewer; - in the same order as the courses; - with an **empty inner list** sitting in the slot of every course that scheduled nothing. Nothing is lost and nothing is invented. The sessions are all there, but they are still **grouped one level deep**, so any later stage - counting sessions, sorting them by start time, selecting the weekend ones - has to open every inner list before it can see a single session. Written as loops, that is a loop inside a loop, and each further one-to-many stage adds another layer. ## Flattening one level Flattening takes a structure whose elements are themselves structures and concatenates the inner ones, in order, into a single structure one level shallower. `[[a, b], [], [c]]` becomes `[a, b, c]`. Two properties are routinely stated wrongly: 1. **It concatenates; it does not merge, sort or de-duplicate.** If two courses both list a shared session, that session appears twice in the flat result. Removing duplicates is a separate step you have to ask for. 2. **It removes exactly one level.** A structure nested three deep flattens to two deep, not to flat. **Transform-then-flatten** - the operation usually called **bind** - is the mapping step and the one-level flatten fused into a single traversal. The result is indistinguishable from doing them in sequence; the fusion matters because it is the composable unit, and because a pipeline built from it never has to name the intermediate structure of structures at all. ## What the counts do | | element-wise mapping | transform-then-flatten | |---|---|---| | result shape | structure of structures | flat structure | | result length | the number of courses | the sum of the per-course session counts | | a course with no sessions | contributes an empty inner structure | contributes nothing | | a course with four sessions | contributes one inner structure | contributes four elements | | order | course order preserved | course order preserved, one course's sessions kept adjacent | The length row is the one interviewers push on. Under mapping the outer length is pinned to the input length, so a course with nothing scheduled still occupies a slot. Under transform-then-flatten the output length is decoupled from the input length entirely: it may be larger, smaller, or zero. ## Reading the shape off the signature You never have to guess which of the two you are holding. If the structure holds `T` and the function you passed has the shape `T -> Structure<U>`: - mapping gives `Structure<Structure<U>>`; - one flatten gives `Structure<U>`; - bind gives `Structure<U>` in a single step. The usual tells that you wanted the second are a stage further down that starts by looping over something it expected to be flat, and a count that comes out equal to the number of courses when somebody asked how many sessions there are. ## Flattening is a choice, not a cleanup Sometimes the grouping *is* the answer. A catalogue page that renders each course with its own session list needs the structure of structures; flattening throws away the only thing that says which sessions belong together. The rule of thumb: flatten when every session is treated alike downstream, keep the nesting when the boundary between groups carries meaning. And note the asymmetry. Grouped to flat is cheap and always available. Flat back to grouped is not - once the inner lists are concatenated, nothing in the result records which course a session came from unless the session already carries that identity. That is why a flattened feed so often grows an extra field on its element type: the group key that somebody has to put back by hand.

  • What happens to the empty inner list when the result is flattened?
    It contributes nothing. Flattening concatenates the inner structures, and concatenating an empty one adds no elements, so a course that scheduled nothing simply vanishes from the flat result. That is why the flat length is the sum of the inner lengths, and can be smaller than the number of courses - or zero, if every course is empty.
  • When is keeping the list of lists the better outcome?
    Whenever the boundary between groups carries meaning: rendering each course with its own session list, counting sessions per course, or applying a per-course rule. Flattening is lossy in one direction - once the inner lists are concatenated, nothing says which course a session came from unless each session already carries that identity.

Each course hands you its own printed session sheet, and some sheets come back blank. Mapping leaves you holding the stack of sheets; flattening is retyping every line onto one continuous list.

saying these in an interview costs you the question

  • Says mapping returns the sessions themselves, not lists of them
  • Thinks a course with no sessions disappears from the mapped result
  • Believes flattening de-duplicates a session listed by two courses
  • Reports the mapped result's length as the session count
  • Assumes flattening reorders sessions instead of concatenating in order
  • Treats the nesting as a bug rather than mapping keeping its promise
open as a page

In a fold that collapses a day of till transactions into one totals record, what do the seed and the combining step each contribute?

level: juniorimportance: must knowfreq 70%

basics

~20 s

The seed is the accumulator's starting value and the result returned for an empty day. The combining step takes the accumulator so far plus one transaction and returns the next accumulator. A fold threads that accumulator through every element.

open as a page

In an order pipeline, how do a mapping stage and a filtering stage each change a collection's size and order?

level: juniorimportance: must knowfreq 76%

basics

~20 s

A mapping stage produces exactly one output per input, so count and order are unchanged and only element values or types change. A filtering stage keeps a subset in the original relative order, so its count can only shrink.

open as a page

A timing wrapper takes a remote-lookup function and returns a new function — why must the returned function keep the original's signature?

level: juniorimportance: must knowfreq 65%

basics

~20 s

Keeping the same parameters and return type makes the wrapper a drop-in: every existing call site keeps working, the wrapper goes in and comes out at one wiring line, and other wrappers can stack around it.

open as a page

What distinguishes currying a three-argument permission check from partially applying its role argument?

level: middleimportance: must knowfreq 66%

basics

~20 s

Currying reshapes the check itself into a chain of one-argument functions, one per parameter, and mentions no argument values. Partial application supplies actual values for some parameters and hands back a single function that still needs the rest.

open as a page

Why is element-wise mapping the wrong step when each course yields zero, one or many sessions and you want one flat list?

level: middleimportance: must knowfreq 55%

basics

~10 s

Element-wise mapping fixes the output count at the input count, so a one-to-many step nests instead of expanding. Transform-then-flatten, or bind, decouples the two counts: an element may contribute nothing, one result, or many.

open as a page

Folding a very long transaction log, what does a left fold do differently from a right fold?

level: middleimportance: must knowfreq 58%

basics

~20 s

They bracket the combinations from opposite ends, so a non-associative step gives different answers. A left fold runs as one loop over a running accumulator; a right fold must reach the far end before its outermost combination can finish, unless evaluation is non-strict.

open as a page

In an eagerly evaluated chain that maps, filters, then maps 10,000 order lines, how many collections get allocated?

level: middleimportance: must knowfreq 61%

basics

~20 s

Three — each eager stage fully builds its own collection before the next one starts: 10,000 elements from the first mapping stage, the survivors from the filtering stage, and that many again from the second mapping stage.

open as a page

A remote lookup is wrapped in retry and then in caching — what changes when the two wrappers swap places?

level: middleimportance: must knowfreq 56%

basics

~20 s

The outer wrapper runs first and decides what the inner one ever sees. With caching outside, a hit returns without any retry happening. With retry outside, every attempt goes through the cache first, so the cache shapes each attempt.

open as a page

A curried permission check over role, resource and action is called with only the role - what comes back?

level: juniorimportance: should knowfreq 46%

basics

~10 s

Another function comes back: one that still expects the resource and, after that, the action. Nothing has been checked yet, and no decision exists until the final argument arrives at the last rung.

open as a page

How should a three-argument permission check order its parameters so the specialised checkers you want are cheap to take?

level: middleimportance: should knowfreq 48%

basics

~20 s

Put the arguments you specialise on - the slow-changing ones such as role and resource - in the leading positions and the per-call argument last, because the usual specialisation helpers and every curried chain consume the parameter list from the left.

open as a page

If mapping a course returns a list of terms, each term a list of sessions, why does one flatten not reach the sessions?

level: middleimportance: should knowfreq 40%

basics

~10 s

Flattening removes exactly one level of nesting, not all of it. Collapsing the per-course level leaves a list of term-lists; reaching the sessions needs a second collapse, one per level of structure.

open as a page

What goes wrong when the seed of a till fold is not an identity for its combining step?

level: middleimportance: should knowfreq 46%

basics

~20 s

The seed's own value is baked into every result. An empty day reports the seed instead of nothing, and if the same fold is later run per chunk the seed is injected once per chunk instead of once overall.

open as a page

When a mapping stage turns order lines into shipment lines, what does it leave untouched and what does the result still share?

level: middleimportance: should knowfreq 54%

basics

~20 s

Untouched means the source collection itself: it is not resized, reordered or written, and the result is a separate collection. Not untouched are the elements — a filtered result holds the very same element objects the source holds.

open as a page

In a course catalogue pipeline, why does a chain of dependent lookups written as nested element-wise maps force an edit at every level when a stage is added?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Each dependent stage can only be written inside the previous one's function, and shape-preserving mapping keeps its result whole, so every stage adds a level of nesting. Adding one changes the shape every enclosing function and the final consumer were written against.

open as a page

A nightly fold over transactions was split across workers and its total now varies per run - what property is missing?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Associativity. Splitting a fold regroups its combinations, and only an associative combining step gives the same answer under every grouping. Varying totals per run mean the chunk boundaries or the merge order are moving and the step can see it.

open as a page

What must a wrapper preserve, beyond the parameter list and return type, so no caller can tell it is there?

level: seniorimportance: should knowfreq 42%

basics

~20 s

The signature is only the floor. A truly invisible wrapper also preserves the failure behaviour, how many times the inner work actually happens, when it happens, and the safety guarantees callers already relied on under concurrency.

open as a page

What do you learn about a fold by rewriting a mapping stage and a filtering stage as folds?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

That a fold is the general one. Seed it with an empty collection and both stages fall out of it, which shows the accumulator's type is free and a fold can rebuild a structure as easily as it can produce a number.

open as a page

Wrappers from a configuration list are applied left to right to a lookup function — which one ends up innermost?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

The first wrapper applied ends up innermost, closest to the original function, and the last one applied ends up outermost. So a call enters through the last entry in the list and reaches the original only after passing every earlier one.

open as a page

A caller of a curried permission check omits one argument - why does that bug surface far from the call site?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Because an under-applied curried call is well formed: it returns the next rung instead of failing. That function value then travels - stored, returned, passed on - and the trouble appears wherever something expects a decision and meets a function.

open as a page

When a transform has no shipment line to produce for some order lines, what must its mapping stage's result still contain?

level: seniorimportance: nice to knowfreq 31%

basics

~20 s

One entry per input, still — a mapping stage cannot skip. The transform has to put a placeholder in those positions, which widens the result's element type, and a separate filtering stage is what removes them afterwards.

open as a page