> For the complete documentation index, see [llms.txt](https://docs.rowsncolumns.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rowsncolumns.app/configuration/features/lambda-and-higher-order-functions.md).

# LAMBDA and higher-order functions

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)   → 5
```

**As 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:

```
=LET(x, 10, LET(y, x*2, y+1))    → 21
```

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:

```
=LET(double, LAMBDA(x, x*2), double(5))    → 10
=LET(add, LAMBDA(a, b, a+b), add(3, 4))    → 7
```

## 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:

```
=LET(n, 5, LAMBDA(x, x+n))
// becomes: __RNC_LAMBDA__("x", "x+(5)")
```

```
=LET(n, 10, LET(addn, LAMBDA(x, x+n), addn(5)))   → 15
```

## Higher-order functions

### MAP

Apply a lambda to each element of an array, returns a same-shape result:

```
=MAP(A1:A3, LAMBDA(v, v*2))
// [[1], [2], [3]] → [[2], [4], [6]]
```

Multiple input arrays of the same shape:

```
=MAP(A1:A3, B1:B3, LAMBDA(a, b, a+b))
```

### REDUCE

Left fold:

```
=REDUCE(0, A1:A3, LAMBDA(acc, v, acc+v))    // sum
=REDUCE(1, A1:A3, LAMBDA(acc, v, acc*v))    // product
```

### SCAN

Like REDUCE but returns intermediate accumulator values, same shape as input:

```
=SCAN(0, A1:A3, LAMBDA(acc, v, acc+v))      // running total
// [[1], [2], [3]] → [[1], [3], [6]]
```

### BYROW / BYCOL

Apply a lambda to each row (or column) as a 1D array:

```
=BYROW(A1:C3, LAMBDA(row, SUM(row)))
=BYCOL(A1:C3, LAMBDA(col, AVERAGE(col)))
```

### MAKEARRAY

Build an N×M array by invoking a lambda with `(r, c)` per cell (1-indexed per Excel):

```
=MAKEARRAY(2, 3, LAMBDA(r, c, r*10+c))
// [[11, 12, 13],
//  [21, 22, 23]]
```

## GROUPBY

Aggregate rows by row-field tuple, calling a lambda once per group on the group's value vector:

```
=GROUPBY(A1:A6, B1:B6, LAMBDA(v, SUM(v)))
```

Args:

| Arg               | Purpose                                                                           |
| ----------------- | --------------------------------------------------------------------------------- |
| `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 |

```
// Sum per category
=GROUPBY(A1:A6, B1:B6, LAMBDA(v, SUM(v)))

// Average per category, descending
=GROUPBY(A1:A6, B1:B6, LAMBDA(v, AVERAGE(v)), 0, 0, -1)

// With grand total at top
=GROUPBY(A1:A6, B1:B6, LAMBDA(v, SUM(v)), 0, 1)

// Count + filter
=GROUPBY(A1:A6, B1:B6, LAMBDA(v, COUNT(v)), 0, 0, 1, C1:C6)
```

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:

```
=PIVOTBY(rows, cols, values, function,
         [row_headers], [row_total_depth], [row_sort_order],
         [col_headers], [col_total_depth], [col_sort_order],
         [filter_array])
```

```
// Rows = regions, columns = quarters, values summed
=PIVOTBY(A1:A8, B1:B8, C1:C8, LAMBDA(v, SUM(v)))

// With row + column grand totals at top/left
=PIVOTBY(A1:A8, B1:B8, C1:C8, LAMBDA(v, SUM(v)), 0, 1, 1, 0, 1, 1)

// Totals at bottom/right
=PIVOTBY(A1:A8, B1:B8, C1:C8, LAMBDA(v, SUM(v)), 0, -1, 1, 0, -1, 1)

// Sort both axes descending
=PIVOTBY(A1:A8, B1:B8, C1:C8, LAMBDA(v, SUM(v)), 0, 0, -1, 0, 0, -1)
```

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:

```
=MAP(A1:A3, LAMBDA(v, v+B1))
// Both A1:A3 AND B1 are tracked as dependencies
```

```
=REDUCE(0, A1:A3, LAMBDA(acc, v, acc+v+C5))
// C5 is tracked
```

## Named LAMBDAs (workbook scope)

Define a named range whose value is a LAMBDA and call it like a function:

```
// In Name Manager:
Commission = =LAMBDA(sales, IF(sales > 1000, sales*0.1, sales*0.05))

// Anywhere in the workbook:
=Commission(500)    → 25
=Commission(5000)   → 500
=MAP(A1:A10, Commission)
```

See [Named ranges](/configuration/features/named-ranges.md) 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.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.rowsncolumns.app/configuration/features/lambda-and-higher-order-functions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
