Skip to content
Back to skills

Functional Folding Over Data

ASecurity

Use when a loop accumulates into a variable, when combining a collection into one value, when the same tree walk keeps being written again, or when a combining law matters.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 21, 2026
ai-agentsjavascripttypescriptgojavaexpress

Works with

  • cli

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned October 21, 2026

npx -y skills add yutna/functional-design-skills --skill functional-folding-over-data --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Functional Folding Over Data?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Functional Folding Over Data
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/yutna-functional-folding-over-data/badge)](https://www.skillsdirectory.com/skills/yutna-functional-folding-over-data)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: functional-folding-over-data
description: Use when a loop accumulates into a variable, when combining a collection into one value, when the same tree walk keeps being written again, or when a combining law matters.
license: MIT
metadata:
  pack: functional-design-skills
  version: 3.3.0
---

# Folding Over Data

**In this pack:** [how much design a task warrants](../functional-design/references/calibration.md),
[the notation](../functional-design/references/notation.md), and
[syntax per language](../functional-design/SKILL.md#language-packs).

## Overview

A fold reduces a structure to a value by combining its elements with a
function. Almost every loop that builds up a result is a fold in
disguise, and rewriting it as one removes the mutable accumulator, the
index, and the off-by-one errors, leaving only the combining step, which
is the part that carries meaning.

The same operation generalises past lists. Any recursive type has a fold:
one function per case, applied bottom-up. That is what replaces the
visitor pattern and most manual tree recursion.

## When to use

- A loop with an accumulator variable
- Summing, counting, grouping, or finding a maximum
- Walking a tree, an expression, or a nested document
- Deriving current state from a list of events
- Replacing a visitor or a recursive helper with an explicit shape

Not for: transforming each element independently, which is a map; or
selecting elements, which is a filter.

## Core rules

1. Default. **Name the accumulator type first.** What is being built determines
   the fold's shape more than the elements do. Naming it is not always declaring
   it: a fold that carries one value of a type that exists, such as a total in
   `Satang`, has its accumulator named already, and a record is declared only
   where the fold carries more than one value.
2. Rule. **The combining function is pure and total.** Same accumulator and
   element, same result, no effect.
3. Default. **Prefer the specific operation when one exists.** `sum`, `maximum`,
   `groupBy` and `count` say more than a fold with a lambda. One exists only for
   the type it was written for: a total of `Satang` is a fold with that type's
   own `add` and `zero`, because `sum` adds plain numbers. Totals per key,
   such as fees per clinician per week, are a `groupBy` and then a fold over
   each bucket. A single fold into a map is for a language or runtime with no
   `groupBy`.
4. Default. **Fold from the left, and reverse once when a list is built.** A
   left fold with a forced accumulator is iterative and constant in stack; see
   the accumulator rule in
   [laziness.md](../functional-using-recursion-and-laziness/references/laziness.md).
   Use a right fold only in a lazy language, or when the consumer takes the
   result a piece at a time. The reverse is for a linked list, whose cheap end
   is the front; a vector or an array that grows at its end is built in order
   and needs none.
5. Default. **Merge two folds into one pass only when they share a predicate or
   an accumulator.** The total and the count of the same treatments are one
   fold. Folds with nothing in common stay as separate passes, because combining
   them obscures all of them. A measurement showing that the extra pass costs is
   the one thing that joins them.
6. Default. **Give a recursive type its fold.** Write it once beside the type,
   and consume the type through it everywhere else. A recursive type that only
   one function walks is matched in that function; its fold is written when the
   walk would otherwise be written a second time.

## Pattern

Loop with accumulators:

```text
total = 0
count = 0
for t in booking.treatments:
  if t.billable:
    total = total + t.price
    count = count + 1
```

Two mutable accumulators, an implicit order, and a shape that cannot be
reused or run in parallel.

Fold:

```text
type Summary = { total: Satang, count: Integer }

addTreatment : Summary -> BookedTreatment -> Summary
addTreatment s t =
  if t.billable
  then { total: add s.total t.price, count: s.count + 1 }
  else s

summarise : List<BookedTreatment> -> Summary
summarise = fold addTreatment { total: zero, count: 0 }
```

`addTreatment` is a pure function that can be tested with two values, and
`summarise` has no mechanism left to get wrong.

## Recognising a fold

| Loop shape                                | Operation      |
| ----------------------------------------- | -------------- |
| Accumulate a single value                 | fold           |
| Build a new collection element-wise       | map            |
| Keep some elements                        | filter         |
| Accumulate, may fail                      | fold + Result  |
| Accumulate while a condition holds        | fold with stop |
| Produce a value per element and one total | fold on a pair |
| Group into buckets                        | groupBy        |
| Find the first match                      | find           |

The signal is the accumulator: any variable declared before the loop and
updated inside it is a fold's accumulator.

## Folding a recursive type

```text
type Expr =
  | Lit of Integer
  | Add of (Expr, Expr)
  | Mul of (Expr, Expr)

foldExpr :
  (Integer -> R) -> (R -> R -> R) -> (R -> R -> R) -> Expr -> R
foldExpr lit add mul e =
  match e with
  | Lit n -> lit n
  | Add (a, b) -> add (go a) (go b)
  | Mul (a, b) -> mul (go a) (go b)
  where go = foldExpr lit add mul

evaluate = foldExpr identity (+) (*)
depth = foldExpr (const 1) (\a b -> 1 + max a b) (\a b -> 1 + max a b)
```

One traversal, written once. Every operation over `Expr` is then three
small functions rather than a new recursive walk with its own chance of
missing a case. This is the visitor pattern with the scaffolding removed.

## Combining with a monoid

When the combining operation has an identity and is associative, folding
becomes splittable and parallelisable, with the parts combined in their
original order, and empty collections stop being a special case.
Reordering the parts needs one more law, commutativity.

```text
-- (add, zero) is a monoid over Satang, which is one currency
totalOf = fold add zero
```

Associativity is what allows a fold to be split across workers and the
partial results combined. See
[monoids.md](references/monoids.md).

## Fold in each language

- **JavaScript and TypeScript.** A fold is `Array.prototype.reduce`,
  and `reduceRight` folds from the right. Always pass the initial value:
  without one, `reduce` on an empty array throws a `TypeError`. An early
  exit is `some`, which stops at the first truthy result. Grouping is
  `Object.groupBy`, or `Map.groupBy` for keys that are not strings.
- **Elixir.** A fold is `Enum.reduce/3`. An early exit is
  `Enum.reduce_while/3`, whose function returns `{:cont, acc}` or
  `{:halt, acc}`. Grouping is `Enum.group_by`, and counting each distinct
  value is `Enum.frequencies`. `Stream` is the lazy counterpart of
  `Enum`.

## Red flags

- A mutable accumulator declared above a loop
- The same traversal of a tree written in three places
- A second recursive helper whose only job is to walk the same structure
- An index variable used only to step through a collection
- Nested loops building a map that a `groupBy` would produce
- A fold whose combining function performs I/O

## Common mistakes

- **Folding when a specific operation exists.** `fold (+) 0` is `sum`
  with more moving parts.
- **Putting effects in the combining function.** The fold then cannot be
  reordered, parallelised, or tested with values. Fold to a description,
  then perform it.
- **Using a right fold on a huge strict list.** In a strict language that
  builds a deep call stack; use a left fold.
- **Merging unrelated folds for speed.** Three clear passes beat one
  clever one unless a measurement says otherwise.
- **Reimplementing the walk each time.** Write the type's fold once.

## Related skills

- [functional-using-recursion-and-laziness](../functional-using-recursion-and-laziness/SKILL.md)
- [functional-composing-functions](../functional-composing-functions/SKILL.md)
- [functional-translating-gof-patterns](../functional-translating-gof-patterns/SKILL.md)
- [functional-managing-state-immutably](../functional-managing-state-immutably/SKILL.md)

## Further reading

- [fold-recipes.md](references/fold-recipes.md) gives the fold for the
  common accumulation shapes, including fallible and early-exit folds.
- [monoids.md](references/monoids.md) explains identity and
  associativity, what they buy, and how to repair an operation that
  fails associativity.

Files in this skill

  • SKILL.md8.4 KB
  • references/fold-recipes.md5.4 KB
  • references/monoids.md4.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.