purrr flatten() in R: Flatten Nested Lists
purrr flatten() in R removes exactly one level of nesting from a list, turning a list of lists into a flatter list. The typed variants flatten_dbl(), flatten_chr(), and flatten_int() return atomic vectors instead.
flatten(x) # remove one nesting level (legacy) list_flatten(x) # modern replacement, returns a list flatten_dbl(x) # flatten to a double vector flatten_chr(x) # flatten to a character vector list_c(x) # concatenate elements into a vector flatten_dfr(x) # row-bind into a data frame (legacy)
Need explanation? Read on for examples and pitfalls.
What purrr flatten() does
flatten() collapses one layer of list hierarchy. Given a list whose elements are themselves lists, it returns a single list containing all the inner elements. It does not touch deeper levels, so a triple-nested list becomes double-nested, not flat.
The function comes from the purrr package, the tidyverse toolkit for functional programming. It is the list-shaping companion to the map() family, which often produces nested output that needs one level removed.
The outer list had two elements; each was a two-element list. After flatten(), the result is a flat list of four scalars.
flatten() syntax and typed variants
flatten() takes a single argument and has one job. The signature is flatten(.x), where .x is the list to un-nest by one level. There are no other parameters. The variety comes from the typed family, which coerces the flattened result into an atomic vector or data frame.
| Variant | Returns | Use when |
|---|---|---|
flatten() |
list | Inner elements have mixed shapes |
flatten_lgl() |
logical vector | Inner elements are TRUE/FALSE |
flatten_int() |
integer vector | Inner elements are integers |
flatten_dbl() |
double vector | Inner elements are numbers |
flatten_chr() |
character vector | Inner elements are strings |
flatten_dfr() / flatten_dfc() |
data frame | Inner elements are rows or columns |
list_flatten(), list_c(), list_rbind(), and list_cbind(). The old functions still work, so existing code will not break, but new code should prefer the list_* family.Flatten examples by use case
The most common use of flatten() is cleaning up map() output. When a mapped function returns a list per element, the result is a list of lists. One flatten() call gives you a flat list you can iterate over again.
A second frequent case is removing exactly one layer from a deeply nested structure while leaving the rest intact.
The result is still nested one level deep. flatten() peeled off the outermost layer only, which is exactly the behavior that separates it from unlist().
flatten() vs list_flatten() and list_c()
Pick the function that matches the shape you want back. flatten() and list_flatten() both return a list and remove one level. list_c() concatenates into a vector. Base R unlist() flattens recursively all the way down.
| Function | Returns | Recursive? | Notes |
|---|---|---|---|
flatten() |
list | No | Superseded in purrr 1.0.0 |
list_flatten() |
list | No | Modern one-level replacement |
list_c() |
atomic vector | n/a | Concatenates inner elements |
unlist() |
atomic vector | Yes | Base R, flattens every level |
flatten_dbl() and friends with a single type-stable function that picks the common type automatically and errors clearly on a real mismatch.Common pitfalls
flatten() is not recursive, so it will not fully un-nest a deep list. This is the most frequent surprise. If you need every level removed, use unlist() or call flatten() repeatedly.
A second trap is feeding mixed types to a typed variant. flatten_dbl() demands every inner element coerce to a double; a stray string stops it cold.
The third pitfall is using flatten() on a list that has no nesting. It returns the list unchanged rather than erroring, which can silently hide a bug where you expected nested input.
Try it yourself
Try it: Flatten the nested list list(list(10, 20), list(30, 40)) into a single numeric vector and save the result to ex_flat.
Click to reveal solution
Explanation: flatten_dbl() removes one nesting level and coerces the four scalars into a double vector. list_c(list(list(10, 20), list(30, 40))) would also work and is the modern equivalent.
Related purrr functions
These functions pair naturally with flatten() when reshaping lists:
list_flatten()removes one nesting level and is the modern replacement forflatten().list_c()concatenates list elements into a single atomic vector.list_rbind()stacks a list of data frames into one tall data frame.compact()drops NULL or empty elements before flattening.pluck()extracts a single deeply nested value by position or name.
See the purrr list-flattening reference for the full family.
FAQ
What is the difference between flatten() and unlist() in R?
flatten() removes exactly one level of nesting and returns a list. unlist() is recursive: it flattens every level and returns an atomic vector. Use flatten() when you want to peel one layer and keep a list structure. Use unlist() when you want a flat vector regardless of how deep the input goes. The typed variants like flatten_dbl() bridge the gap by removing one level and coercing to a vector.
Is purrr flatten() deprecated?
flatten() is superseded, not deprecated, as of purrr 1.0.0. Superseded means it still works and will not be removed, but it is no longer recommended for new code. The replacements are list_flatten() for list output and list_c(), list_rbind(), or list_cbind() for vector and data frame output. Existing scripts that call flatten() continue to run without warnings.
How do I flatten a nested list in R without purrr?
Base R offers unlist(), which flattens recursively, and do.call(c, x), which concatenates one level. For a one-level list-to-list flatten without purrr, do.call(c, x) is the closest equivalent. If you only need a vector, unlist(x) is simplest, though it strips all nesting at once.
Does flatten() work recursively?
No. flatten() removes only the outermost level of nesting. A list nested three levels deep becomes nested two levels deep after one call. To fully flatten, call flatten() multiple times or use unlist(), which recurses through every level by default.