purrr::reduce

Updated April 22, 2026 · Tidyverse
r purrr tidyverse reduce fold accumulate functional-programming

Overview

reduce() takes a binary function (a function that takes two arguments) and applies it cumulatively to a list or vector. Given [a, b, c] and a function f, it computes f(f(a, b), c). This is called a “fold” or “accumulation” in functional programming.

It’s useful when you have a list of things that need to be combined into one value — merging data frames, building up a string, composing functions, or chaining operations.

Basic Usage

library(purrr)

# Sum numbers by folding addition
nums <- c(1, 2, 3, 4, 5)
reduce(nums, `+`)
#> [1] 15

# That's equivalent to ((((1 + 2) + 3) + 4) + 5)

The function + is passed infix-style using the backtick syntax. You can also use a lambda:

reduce(c(1, 2, 3, 4, 5), \(acc, x) acc + x)
#> [1] 15

Providing an Initial Value

Use .init to start with a seed value:

# Start from 10, then add each element
reduce(c(1, 2, 3), `+`, .init = 10)
#> [1] 16

When .init is provided, the first call is f(.init, first_element) instead of f(first_element, second_element).

Chaining with Pipes

reduce() fits naturally in a %>% pipeline:

library(dplyr)

list(
  tibble(x = 1:3, y = c("a", "b", "c")),
  tibble(x = 4:6, y = c("d", "e", "f")),
  tibble(x = 7:9, y = c("g", "h", "i"))
) |>
  reduce(bind_rows)
#> # A tibble: 9 x 2
#>   x     y
#>   <int> <chr>
#> 1     1 a
#> 2     2 b
#> 3     3 c
#> 4     4 d
#> 5     5 e
#> 6     6 f
#> 7     7 g
#> 8     8 h
#> 9     9 i

Here bind_rows() takes two data frames and combines them. reduce() applies it repeatedly until all data frames are merged.

merge()ing Named Lists

configs <- list(
  list(host = "localhost", port = 5432),
  list(user = "alice", password = "secret"),
  list(db = "production")
)

reduce(configs, function(acc, x) {
  modifyList(acc, x)
})
#> $host
#> [1] "localhost"
#>
#> $port
#> [1] 5432
#>
#> $user
#> [1] "alice"
#>
#> $password
#> [1] "secret"
#>
#> $db
#> [1] "production"

modifyList() recursively merges two lists, so reduce() merges all of them together.

Composing Functions

A functional programming classic — build up a composed function from right to left:

f <- function(x) x + 1
g <- function(x) x * 2
h <- function(x) x - 3

# ((x - 3) * 2) + 1 — note the order is right to left
pipeline <- reduce(list(f, g, h), compose)

pipeline(10)
#> [1] ((10 - 3) * 2) + 1 = 15

reduce() with compose() builds the function composition. Note that compose() applies right-to-left, so the last function in the list is applied first.

reduce() vs for loops

reduce() replaces accumulation loops with a functional style:

# Traditional for loop
result <- 1
for (x in c(2, 3, 4, 5)) {
  result <- result * x
}

# reduce() version
result <- reduce(c(2, 3, 4, 5), `*`, .init = 1)

The reduce() version makes the accumulation intent explicit and avoids mutable state.

reduce_right() — Fold from the Right

reduce_right() folds from the last element to the first. Given [a, b, c] it computes f(a, f(b, c)):

# reduce from left: (((1 - 2) - 3) - 4)
reduce(c(1, 2, 3, 4), `-`)
#> [1] -8

# reduce from right: (1 - (2 - (3 - 4)))
reduce_right(c(1, 2, 3, 4), `-`)
#> [1] -2

For commutative operations like + and *, the result is the same. For non-commutative operations like string concatenation or matrix multiplication, the direction matters.

accumulate() — See Intermediate Results

accumulate() does the same as reduce() but returns all intermediate results:

accumulate(c(1, 2, 3, 4, 5), `+`)
#> [1]  1  3  6 10 15

Compare to reduce() which only returns the final value:

reduce(c(1, 2, 3, 4, 5), `+`)
#> [1] 15

accumulate() is useful when you want to inspect or plot the progression of the accumulation.

reduce2() and accumulate2() — Three-Argument Functions

reduce2() and accumulate2() pass three arguments to the function: the accumulator, the current element, and an extra vector:

# For operations that need an additional parameter
files <- c("a.csv", "b.csv", "c.csv")

reduce2(
  files,
  function(acc, file, sep) {
    if (is.null(acc)) {
      readLines(file)
    } else {
      c(acc, readLines(file))
    }
  },
  .init = NULL
)

This pattern is useful when the combining function needs extra data that isn’t in the list being reduced.

Short-Circuiting with done()

From purrr 1.0, you can terminate an accumulation early by returning done(value) from the reducing function:

library(purrr)

# Find the first value that exceeds a threshold
values <- c(1, 2, 5, 3, 8, 12, 15)

accumulate(values, function(acc, x) {
  if (x > 10) done(x) else x
})
#> [1]  1  2  5  3  8 12

The accumulation stops at the first done(), returning that value and discarding the rest.

Empty Input

When the input is empty, reduce() errors by default. Provide .init to handle this case:

reduce(integer(), `+`)
#> Error: `x` must have at least 1 record

reduce(integer(), `+`, .init = 0)
#> [1] 0

Common Use Cases

Merging lists or data framesreduce(list_of_dfs, full_join) combines multiple tables by a common key.

Building a character stringreduce(vec, paste0) concatenates strings.

Chaining transformationsreduce(list(fn1, fn2, fn3), compose) builds a processing pipeline.

Set operationsreduce(list_of_sets, union) finds the union of many sets.

See Also