100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Higher-Order Functions in Erlang

How Erlang treats functions as first-class values, enabling anonymous funs, closures, and the standard library's map/filter/foldl.

Core ConceptsIntermediate9 min readJul 10, 2026
Analogies

Functions as First-Class Citizens

In Erlang, functions are ordinary values: you can bind a function to a variable, pass it as an argument to another function, store it in a list or tuple, and return it as a result — this is what it means for functions to be first-class. A higher-order function is simply a function that accepts another function as an argument, returns a function, or both, and this capability is what lets Erlang express generic operations like 'do this to every element' or 'combine every element' without writing a bespoke loop for each specific case.

🏏

Cricket analogy: Treating a bowling strategy as something you can hand to any captain — not baked into one specific match plan — is like Erlang treating functions as values; a higher-order function like 'apply this strategy to every over' works for any strategy you pass it, instead of writing a new match plan for each bowler.

Anonymous Functions (Funs)

An anonymous function, or fun, is written fun(X) -> X * X end and can be assigned to a variable like Square = fun(X) -> X * X end, then invoked as Square(4). Funs are closures: a fun created inside another function automatically captures the variables in scope at the point it's defined, so fun(X) -> X + Base end. remembers whatever value Base had when the fun was created, even after the enclosing function has returned and that variable would otherwise be gone.

🏏

Cricket analogy: Square = fun(X) -> X * X end is like a coach writing down a specific drill on a card and handing it to any player to run; and a fun that captures Base, the way fun(X) -> X + Base end. remembers a specific target score set before play started, keeps using that target even after the team meeting where it was set has ended.

erlang
-module(hof_examples).
-export([apply_twice/2, doubled/1, evens/1, total/1, reversed/1]).

apply_twice(F, X) -> F(F(X)).

doubled(List) ->
    lists:map(fun(X) -> X * 2 end, List).

evens(List) ->
    lists:filter(fun(X) -> X rem 2 =:= 0 end, List).

total(List) ->
    lists:foldl(fun(X, Sum) -> X + Sum end, 0, List).

reversed(List) ->
    (fun lists:reverse/1)(List).

lists:map, lists:filter, lists:foldl

The lists module ships three workhorse higher-order functions: lists:map(Fun, List) applies Fun to every element and returns a new list of the results, such as lists:map(fun(X) -> X * 2 end, [1,2,3]) yielding [2,4,6]; lists:filter(Pred, List) keeps only the elements for which Pred returns true, such as lists:filter(fun(X) -> X rem 2 =:= 0 end, [1,2,3,4]) yielding [2,4]; and lists:foldl(Fun, Acc0, List) reduces the whole list down to a single accumulated value by threading Fun and a running accumulator through every element, the way lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3]) yields 6.

🏏

Cricket analogy: lists:map is like converting every batsman's raw score to strike rate across the whole scorecard in one pass; lists:filter is like keeping only the batsmen who scored a half-century; and lists:foldl is like totting up the entire team's total runs by adding one player's score to a running total at a time.

The fun Module:Function/Arity syntax, such as fun lists:reverse/1, lets you pass an already-defined named function anywhere a fun is expected, without the overhead or extra clutter of wrapping it as fun(X) -> lists:reverse(X) end — both work identically at the call site.

Writing Your Own Higher-Order Functions

You can define your own higher-order functions the same way the standard library does, accepting a fun as a parameter and applying it inside, as in apply_twice(F, X) -> F(F(X))., which calls apply_twice(fun(X) -> X + 3 end, 10) to get 16. Erlang also lets you reference an already-named function as a fun value using the fun Module:Function/Arity syntax, so fun lists:reverse/1 can be passed anywhere a fun is expected without wrapping it in an extra anonymous fun.

🏏

Cricket analogy: apply_twice(F, X) -> F(F(X)) is like running a specific fielding drill on a player twice in a row to reinforce it; and referencing fun lists:reverse/1 directly, instead of wrapping it, is like a coach directly assigning a named specialist coach to a drill roster rather than describing the drill from scratch each time.

A fun created inside a loop-like recursive helper captures whatever variables were in scope at that point, which is convenient for closures but can also accidentally hold references to large data structures longer than expected if the fun itself is stored and kept alive well past when that data is needed.

  • Functions are first-class values in Erlang: they can be assigned to variables, passed as arguments, and returned from other functions.
  • A higher-order function accepts a function as an argument, returns one, or both.
  • Anonymous functions (funs) are written fun(Args) -> Body end and can be invoked like Fun(Args).
  • Funs are closures: they automatically capture the variables in scope at their point of definition.
  • lists:map/2 transforms every element; lists:filter/2 keeps elements matching a predicate; lists:foldl/3 reduces a list to one value.
  • fun Module:Function/Arity references an existing named function as a fun value without wrapping it.
  • Custom higher-order functions can accept and apply funs exactly the way the standard library's do.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ErlangStudyNotes#HigherOrderFunctionsInErlang#Higher#Order#Functions#Erlang#StudyNotes#SkillVeris#ExamPrep