Mojo Higher Order Functions
A higher-order function is a function that accepts another function as an argument, returns a function, or both. Higher-order functions let you express patterns like "apply this operation to every element" or "keep only elements that satisfy this condition" without writing repetitive loops. They make code shorter, more composable, and easier to reason about.
The Assembly Line Analogy
Without higher-order functions:
Loop 1: double every item
Loop 2: filter items above 10
Loop 3: sum the remaining items
→ Three separate loops, all similar boilerplate
With higher-order functions:
map(double, items) → new list with every item doubled
filter(above_10, items) → new list with only large items
reduce(add, items, 0) → one number: the sum
Each function is a specialized assembly station.
You chain stations together to build a pipeline.
map — Apply a Function to Every Element
fn map_list(data: List[Int], func: fn(Int) -> Int) -> List[Int]:
var result = List[Int]()
for i in range(len(data)):
result.append(func(data[i]))
return result
fn double(x: Int) -> Int: return x * 2
fn square(x: Int) -> Int: return x * x
fn negate(x: Int) -> Int: return -x
fn main():
var nums = List[Int](1, 2, 3, 4, 5)
var doubled = map_list(nums, double)
var squared = map_list(nums, square)
var negated = map_list(nums, negate)
for i in range(len(doubled)):
print(doubled[i], end=" ") # 2 4 6 8 10
print("")
for i in range(len(squared)):
print(squared[i], end=" ") # 1 4 9 16 25
print("")
map diagram:
Input: [1, 2, 3, 4, 5]
│ │ │ │ │
double double ... double
│ │ │ │ │
Output: [2, 4, 6, 8, 10]
filter — Keep Elements That Satisfy a Condition
fn filter_list(data: List[Int], predicate: fn(Int) -> Bool) -> List[Int]:
var result = List[Int]()
for i in range(len(data)):
if predicate(data[i]):
result.append(data[i])
return result
fn is_even(x: Int) -> Bool: return x % 2 == 0
fn is_positive(x: Int) -> Bool: return x > 0
fn above_threshold(x: Int) -> Bool: return x > 3
fn main():
var nums = List[Int](1, 2, 3, 4, 5, 6, 7, 8)
var mixed = List[Int](-3, -1, 0, 2, 5, -7, 8)
var evens = filter_list(nums, is_even)
var positives = filter_list(mixed, is_positive)
var big = filter_list(nums, above_threshold)
for i in range(len(evens)):
print(evens[i], end=" ") # 2 4 6 8
print("")
for i in range(len(positives)):
print(positives[i], end=" ") # 2 5 8
print("")
filter diagram: Input: [1, 2, 3, 4, 5, 6, 7, 8] Test: odd even odd even odd even odd even Keep: ✗ ✓ ✗ ✓ ✗ ✓ ✗ ✓ Output: [2, 4, 6, 8]
reduce — Collapse a List to One Value
fn reduce_list(data: List[Int], func: fn(Int, Int) -> Int, initial: Int) -> Int:
var accumulator = initial
for i in range(len(data)):
accumulator = func(accumulator, data[i])
return accumulator
fn add(a: Int, b: Int) -> Int: return a + b
fn multiply(a: Int, b: Int) -> Int: return a * b
fn max_of(a: Int, b: Int) -> Int: return a if a > b else b
fn main():
var nums = List[Int](1, 2, 3, 4, 5)
var total = reduce_list(nums, add, 0) # 15
var product = reduce_list(nums, multiply, 1) # 120
var maximum = reduce_list(nums, max_of, nums[0]) # 5
print("Sum:", total) # Sum: 15
print("Product:", product) # Product: 120
print("Max:", maximum) # Max: 5
reduce with add, initial=0: Step 1: acc=0, func(0, 1) = 1 Step 2: acc=1, func(1, 2) = 3 Step 3: acc=3, func(3, 3) = 6 Step 4: acc=6, func(6, 4) = 10 Step 5: acc=10, func(10, 5) = 15 Result: 15
Chaining map, filter, and reduce
fn main():
var scores = List[Int](45, 72, 88, 33, 91, 65, 78)
# Step 1: keep only passing scores (>= 60)
var passing = filter_list(scores, fn(x: Int) -> Bool: return x >= 60)
# Step 2: apply a 10% bonus to each passing score
var bonused = map_list(passing, fn(x: Int) -> Int: return x + x // 10)
# Step 3: find the total of bonused scores
var total = reduce_list(bonused, fn(a: Int, b: Int) -> Int: return a + b, 0)
print("Passing scores:", len(passing)) # 5
print("Total after bonus:", total)
Pipeline diagram:
[45,72,88,33,91,65,78]
│
filter(>=60)
│
[72, 88, 91, 65, 78]
│
map(+10%)
│
[79, 96, 100, 71, 85]
│
reduce(+)
│
431
apply_n — Repeat an Operation N Times
fn apply_n(func: fn(Int) -> Int, value: Int, times: Int) -> Int:
var result = value
for _ in range(times):
result = func(result)
return result
fn main():
var doubled_5_times = apply_n(double, 1, 5)
print(doubled_5_times) # 32 (1→2→4→8→16→32)
var squared_3_times = apply_n(square, 2, 3)
print(squared_3_times) # 256 (2→4→16→256)
Generic Higher-Order Function
fn apply_to_pair[T: AnyType, R: AnyType](
a: T, b: T, func: fn(T, T) -> R
) -> R:
return func(a, b)
fn main():
fn sum_ints(x: Int, y: Int) -> Int: return x + y
fn max_floats(x: Float64, y: Float64) -> Float64:
return x if x > y else y
print(apply_to_pair[Int, Int](10, 20, sum_ints)) # 30
print(apply_to_pair[Float64, Float64](3.5, 7.2, max_floats)) # 7.2
Higher-Order Functions vs Explicit Loops
Explicit loop: Higher-order function:
var result = List[Int]() var result = map_list(data, double)
for i in range(len(data)):
result.append(data[i]*2)
Both produce the same output.
Higher-order functions:
✓ Less code to write and read
✓ Name the intent (map, filter, reduce) not the mechanism
✓ Reuse the same operation across different data
✓ Easy to chain into pipelines
✗ Slight overhead for very tight inner loops (use vectorize there)
Key Takeaways
A higher-order function takes another function as an argument or returns one. map transforms every element using a function. filter keeps only elements where a predicate returns true. reduce collapses a list to a single value by applying a combining function repeatedly. Chain these three patterns to build expressive data pipelines. Pass named functions or inline closures as arguments — Mojo treats both identically. Use higher-order functions for clarity on general data transformations and use vectorize for maximum performance on tight numerical loops.
