LAMBDA and higher-order functions
LAMBDA, LET, MAP/REDUCE/BYROW/BYCOL/SCAN/MAKEARRAY, and GROUPBY/PIVOTBY
The formula engine ships Excel's full LAMBDA family: LAMBDA as both an IIFE and a value, LET for named bindings, the six standard higher-order functions (MAP, REDUCE, BYROW, BYCOL, SCAN, MAKEARRAY), and GROUPBY / PIVOTBY built on the same infrastructure. Dependency tracking through lambda bodies is automatic.
LAMBDA
Two shapes — both work:
IIFE (call inline):
=LAMBDA(x, x*x)(5) → 25
=LAMBDA(a, b, a+b)(2, 3) → 5As a value (pass to a HOF or store on a named range):
=MAP(A1:A3, LAMBDA(v, v*2))Internally a standalone LAMBDA(...) rewrites at compile time to __RNC_LAMBDA__("params", "body source"), which constructs a runtime lambda value {__isLambda, params, body}. HOFs unwrap that value and re-evaluate the body once per element with bindings substituted in.
A recursion guard caps depth at 256 to keep runaway lambdas from blowing the JS stack.
LET
Bind names to values inside a formula. Source-level expansion: each binding's value is inlined into the body before parse.
=LET(n, 5, n*2) → 10
=LET(a, 3, b, 4, a+b) → 7
=LET(name, "World", "Hello "&name) → "Hello World"Nested LET works:
A LET-bound LAMBDA can be called like a function — the engine substitutes Function-shaped occurrences of binding names with the lambda source, then the IIFE inliner collapses it:
Closures via LET capture
LET expands BEFORE LAMBDA, so any LET binding referenced inside a LAMBDA body is baked into the body source. This gives you closures for free:
Higher-order functions
MAP
Apply a lambda to each element of an array, returns a same-shape result:
Multiple input arrays of the same shape:
REDUCE
Left fold:
SCAN
Like REDUCE but returns intermediate accumulator values, same shape as input:
BYROW / BYCOL
Apply a lambda to each row (or column) as a 1D array:
MAKEARRAY
Build an N×M array by invoking a lambda with (r, c) per cell (1-indexed per Excel):
GROUPBY
Aggregate rows by row-field tuple, calling a lambda once per group on the group's value vector:
Args:
row_fields
column or 2D — each row defines a group key tuple
values
column or 2D — values to aggregate per group
function
LAMBDA receiving the group's value vector (1D array) and returning a scalar
[field_headers]
0 (default) / 1 — echo the leading header row in output
[total_depth]
0 (default) / 1 grand-total at top / -1 grand-total at bottom
[sort_order]
1 (default, asc) / -1 (desc) by the first row-field column
[filter_array]
optional boolean column same length as row_fields; rows where FALSE are excluded
The aggregation lambda receives the group's value vector as an Excel array literal ({a, b, c}) substituted into the body source — so any aggregator that accepts an array works (SUM, AVERAGE, COUNT, MAX, MIN, custom LAMBDAs, etc.).
PIVOTBY
Two-axis cousin of GROUPBY — adds a column axis on top of the row grouping:
Output is a 2D spilled array — a header row of distinct column-tuples, then one row per distinct row-tuple prefixed by row labels.
Dependency tracking through lambda bodies
When a lambda body references a cell, the dep parser walks the body string via a fresh inner parser instance and registers each ref as a precedent of the outer formula. Recalc propagates correctly:
Named LAMBDAs (workbook scope)
Define a named range whose value is a LAMBDA and call it like a function:
See Named ranges for the full story on workbook + sheet scoping.
What's not yet supported
Recursive LAMBDA — =LET(fact, LAMBDA(n, IF(n<=1, 1, n*fact(n-1))), fact(5)) doesn't work yet. Body-reparse at runtime loses the LET binding for fact. A runtime registry that survives per-call re-parse is the fix; deferred.
See docs/getting-started/excel-google-sheet-compatibility.md → "Phase 7" for the full status.
Last updated