purrr::walk()

walk(.x, .f, ...)
Returns: NULL (invisible .x) · Updated March 13, 2026 · Tidyverse
purrr walk functional-programming tidyverse

The walk() and walk2() functions from the purrr package apply a function to each element of a list or vector for its side effects—they return the input .x invisibly instead of collecting results. This is useful when you want to perform an action (printing, saving, plotting) on each element without constructing a new list. walk() iterates over a single vector, while walk2() iterates over two vectors in parallel, matching elements by position. Together they provide a clean, pipe‑friendly way to execute loops that produce side effects, keeping your code readable and functional.

Syntax

walk(.x, .f, ...)

walk2(.x, .y, .f, ...)

Parameters

ParameterTypeDefaultDescription
.xA vector or list(required)First vector to iterate over.
.yA vector or list(required)Second vector to iterate over (for walk2 only).
.fA function, formula, or vector(required)Function to apply to each element (or pair). If a formula, e.g. ~ .x + 1, it is converted to a function with .x (and .y for walk2) as arguments.
...Additional argumentsNoneExtra arguments passed on to .f.

Examples

Basic walk() for printing

library(purrr)

# Create a list of data frames
df_list <- list(
  mtcars[1:3, ],
  iris[1:3, ],
  airquality[1:3, ]
)

# Print each data frame (side effect)
walk(df_list, print)
#                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
# Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
# Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
# Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
# 
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# 1          5.1         3.5          1.4         0.2  setosa
# 2          4.9         3.0          1.4         0.2  setosa
# 3          4.7         3.2          1.3         0.2  setosa
# 
#   Ozone Solar.R Wind Temp Month Day
# 1    41     190  7.4   67     5   1
# 2    36     118  8.0   72     5   2
# 3    12     149 12.6   74     5   3

# The walk() call returns the original df_list invisibly,
# allowing further piping.
result <- walk(df_list, print)
class(result)  # "list"
length(result) # 3

walk() with anonymous function for side effects

# Create a directory of CSV files
temp_dir <- tempdir()
walk(1:3, ~ write.csv(mtcars[1:3, ], file.path(temp_dir, paste0("mtcars_", .x, ".csv"))))

# List the created files
list.files(temp_dir, pattern = "mtcars_.*\\.csv")
# [1] "mtcars_1.csv" "mtcars_2.csv" "mtcars_3.csv"

walk2() for parallel iteration

# Two vectors: file names and data frames
file_names <- c("cars.csv", "iris.csv", "air.csv")
data_list <- list(mtcars, iris, airquality)

# Save each data frame with its corresponding name
walk2(data_list, file_names, ~ write.csv(.x, .y))

# Check that files exist
file.exists(file_names)
# [1] TRUE TRUE TRUE

walk2() with formula syntax

# Create a series of simple scatter plots
x_vals <- list(1:10, 11:20, 21:30)
y_vals <- list(rnorm(10), rnorm(10), rnorm(10))

walk2(x_vals, y_vals, ~ plot(.x, .y, main = paste("Plot for", .y[1])))
# Three plot windows appear (side effect).

Common Patterns

Iterating with side effects in a pipeline

Because walk() returns its input invisibly, you can insert side‑effect steps into a pipeline without breaking the flow:

library(dplyr)
library(purrr)

mtcars %>%
  split(.$cyl) %>%
  walk(~ print(summary(.$mpg))) %>%
  map(~ lm(mpg ~ wt, data = .)) %>%
  walk2(c("cyl4", "cyl6", "cyl8"), ~ {
    png(paste0(.y, ".png"))
    plot(.x)
    dev.off()
  })

Validating data without storing results

Use walk() to run validation checks, throwing an error if any check fails:

validate_data <- function(df) {
  walk(names(df), ~ if (any(is.na(df[[tidyverse::]]))) stop("Column ", .x, " contains NA"))
  walk(names(df), ~ if (!is.numeric(df[[tidyverse::]])) warning("Column ", .x, " is not numeric"))
  invisible(df)
}

validate_data(mtcars)  # passes silently
# validate_data(iris)  # would warn about Species column

Batch file operations

Combine walk() with fs::dir_create() and walk2() with readr::write_csv() to create and populate directories:

library(fs)
library(readr)

# Create a directory for each species
species <- unique(iris$Species)
walk(species, ~ dir_create(.x))

# Write a CSV for each species into its directory
iris_split <- split(iris, iris$Species)
walk2(iris_split, species, ~ write_csv(.x, path(.y, "data.csv")))

See Also