Higher-Order Functions
Functions that take other functions as arguments or return them, enabling powerful abstraction.
Overview
A higher-order function either takes one or more functions as arguments, returns a function, or both. They are the backbone of functional composition, enabling behaviour to be parameterised and combined without subclassing or inheritance.
Origin
Higher-order functions are a direct consequence of the lambda calculus (Church, 1930s). Languages like LISP, APL, and ML embraced them early. They entered mainstream programming through JavaScript, Ruby, and Python, and are now present in virtually every modern language.
Examples
Map, filter, and reduce as higher-order functions
const orders = [
{ id: 1, total: 120, status: 'completed' },
{ id: 2, total: 45, status: 'pending' },
{ id: 3, total: 300, status: 'completed' },
]
const totalRevenue = orders
.filter(o => o.status === 'completed')
.map(o => o.total)
.reduce((sum, total) => sum + total, 0)
// => 420Function composition and pipelines in Ruby
double = ->(x) { x * 2 }
add_ten = ->(x) { x + 10 }
square = ->(x) { x * x }
# Ruby 2.6+ compose operator
pipeline = double >> add_ten >> square
pipeline.call(3) # => ((3 * 2) + 10)^2 = 256
# Building a configurable validator
def build_validator(*rules)
->(value) { rules.all? { |rule| rule.call(value) } }
end
not_blank = ->(v) { !v.nil? && !v.empty? }
min_length = ->(n) { ->(v) { v.length >= n } }
no_spaces = ->(v) { !v.include?(' ') }
valid_username = build_validator(not_blank, min_length.call(3), no_spaces)
valid_username.call('jarred') # => true
valid_username.call('j r') # => falseUse Cases
- 01Data transformation pipelines (ETL, report generation)
- 02Middleware and plugin systems that wrap behaviour without modifying originals
- 03Event callbacks and promise chaining
- 04Building configurable validators, formatters, or strategies without subclassing
- 05Lazy sequences and generators that apply transformations on demand
When Not to Use
- //When chaining many anonymous functions makes the call stack opaque in error traces, named functions improve debugging
- //When the team is primarily procedural and the indirection adds confusion rather than clarity
- //For simple one-off transformations where a direct loop is more readable
Technical Notes
- Prefer named functions over anonymous arrow functions in long pipelines, stack traces show the function name
- In Ruby, Symbol#to_proc (&:method) is shorthand for a common pattern: array.map(&:upcase) vs array.map { |s| s.upcase }
- Functions returned from other functions capture the enclosing scope as closures, be aware of what they hold onto
- Higher-order functions enable the Strategy, Decorator, and Command design patterns without class hierarchies
More in Programming Techniques