Higher-Order Functions
Functions that take functions, return functions, or both — and why this single idea is the engine of LINQ
A higher-order function (HOF) is a function that does at least one of these two things:
- Takes another function as an argument.
- Returns a function as its result.
That's the whole definition. It sounds abstract, but you have already
been using HOFs every time you wrote .Where(n => n % 2 == 0).
Where is a higher-order function: it takes another function (the
predicate) as a parameter.
This page is about why that idea is so powerful, and how to wield it in everyday C#.
The pattern before the name
Look at three "search" methods written in classic imperative style:
These three methods are the same method except for one
expression: the condition inside the if. That's a code smell. The
behavior is what differs, but we've duplicated everything else.
A higher-order function lets us extract behavior as an argument:
One method, three different conditions, zero duplication. That is
the LINQ idea in miniature — and it's exactly what Enumerable.First
is, just slightly more polished.
The two function shapes you'll see everywhere
Almost every higher-order function in C# uses one of these two generic delegates:
| Delegate | Shape | Used for |
|---|---|---|
Func<TIn, TOut> | A function that returns a value | Mapping, projecting, computing |
Action<T> | A function that returns void | Side effects (printing, writing) |
There's a special case for predicates:
| Delegate | Shape | Used for |
|---|---|---|
Func<T, bool> | A function returning bool | Filtering — also written Predicate<T> |
These are values, just like int or string. You can store them in
variables, pass them around, return them, put them in lists.
Returning a function
This is the other half of "higher-order". A function that returns a function is sometimes called a function factory.
Multiplier(2) returns a new function that remembers factor=2.
Multiplier(3) returns a different function. The captured factor
is part of the returned function's "personality".
A practical example — a predicate factory:
You can build a library of predicates this way without writing each one from scratch.
Composing functions
Functions are values, so you can write a function that composes two other functions: feed the output of one into the other.
Compose is the operation in functional programming. Composing
small pure functions is how you build large transformations without
ever writing a for loop.
Why this is the engine of LINQ
Every LINQ operator is a higher-order function:
Each box accepts a function and uses it to drive its behavior over a sequence. The sequence itself is just data; the behavior is parameterized.
This is the cleanest possible separation of:
- Structure — the operator (iterate, branch, accumulate)
- Policy — the function you pass in
You can change the policy without rewriting the structure, and you can reuse the structure across hundreds of policies.
A practical pattern: configurable transformations
A real example: a configurable text "pipeline" built from small named functions.
The same Pipeline function builds different cleaners depending on
which steps you pass in. The steps themselves are pure, tiny, and
testable in isolation.
A multi-file challenge
Implement a small reusable HOF utility.
Implement Filters.Build, a higher-order function that takes
a predicate (Func<int, bool>) and returns a function that, given a
list of ints, returns only those that match the predicate (in order).
Signature:
public static Func<IEnumerable<int>, IEnumerable<int>> Build(Func<int, bool> predicate)
Program.cs uses it to build an "is positive" filter and prints the
matching numbers, one per line.
Which of the following is not a higher-order function?
Enumerable.Where — takes a Func<T, bool> predicate.
Enumerable.Select — takes a Func<TSource, TResult> selector.
A "Multiplier" method that returns Func<int, int> for a given factor.
static int Add(int x, int y) => x + y;
Immutability
Why values that never change make programs simpler, safer, and easier to compose — and how modern C# expresses immutability ergonomically
Lambdas and Delegates
How C# represents functions as values — and the subtle but important differences between lambdas, delegates, methods, and expression trees