Written and maintained by CASRAI Editorial Board
Last updated
A for loop in R repeats a block of code once for each element of a vector or list: for (var in seq) expr. The part most tutorials skip is what R’s own reference page guarantees about it: the sequence is evaluated once, a zero-length sequence skips the body, and the loop itself returns nothing, so every result you want must be stored explicitly. This guide covers looping in R programming from that reference, then shows when lapply, sapply or vapply is the better tool.
What looping in R is used for in research
Research analysis repeats the same step many times: fit one model per variable, read one file per participant, compute one summary per group. R is a statistical programming language, and the R Core Team’s An Introduction to R says its programming language includes “conditionals, loops, user defined recursive functions and input and output facilities” (see what R programming is used for). A loop is the most direct way to express “do this for each item”, and it is the form that is easiest to read back months later when you revisit a script. For reproducible work, loops belong in a script with a pinned package environment, as described in the renv guide.
Install or access
Loops are part of the base language, so no package is needed. Install R from the Comprehensive R Archive Network (CRAN) at cran.r-project.org, the project’s official distribution site, and type the examples below at the R console or in a script. The authoritative references for everything here are the R manual pages “Control Flow” and “Apply Functions Over List or Vector”, and section 9 (“Grouping, loops and conditional execution”) of An Introduction to R; all three are linked under Sources.
Step by step: write a for loop
- Choose the sequence. The R reference defines the form as
for(var in seq) expr, whereseqis “an expression evaluating to a vector (including a list and an expression) or to a pairlist or NULL”. Here the sequence is a numeric vector:x <- c(4, 8, 15) - Loop over positions with
seq_along. The reference suggestsseq_alongto get an index vector and then extracting individual elements explicitly (it says this for classed objects, and it works for any vector):for (i in seq_along(x)) { print(x[i] * 2) }prints 8, 16 and 30. - Create a container before the loop and fill it.
doubled <- numeric(length(x)), then inside the loopdoubled[i] <- x[i] * 2. After the loop,doubledholds 8, 16, 30. - Skip or stop early when needed. Per the reference,
nexthalts the current iteration and advances the looping index, andbreaktransfers control to the first statement outside the innermost loop. Both apply only to the innermost of nested loops. For example,for (v in x) { if (v == 8) next; print(v) }prints 4 and 15. - Loop directly over values when you do not need the position.
for (v in x) print(v)visits 4, 8 and 15 in order. Use the index form from step 2 when you also need to write back into a container at the same position.
Output and how to read it
Three guarantees from the “Control Flow” reference explain most surprises:
- The loop returns nothing useful. “for, while and repeat return NULL invisibly.” Assigning
result <- for (...) ...givesNULL, so store values inside the body, as in step 3. - The loop variable survives. After the loop,
varremains a variable holding its latest value. “for sets var to the last used element of seq, or to NULL if it was of length zero.” After the loop overxabove,vequals 15. - The sequence is fixed at the start. “The seq in a for loop is evaluated at the start of the loop; changing it subsequently does not affect the loop.” You can assign to the loop variable inside the body, but this does not affect the next iteration.
Troubleshooting
The loop body never runs
“If seq has length zero the body of the loop is skipped.” An empty vector, an empty list or NULL therefore produces no iterations and no error. This is the usual explanation when a loop over filtered data appears to do nothing: check the length of the sequence first.
Loops over 1:length(x) misbehave on empty input
The expression 1:length(x) becomes 1:0 when x is empty, which is the two-element sequence 1, 0, so the body runs twice with invalid positions. seq_along(x) is empty for an empty x, so by the zero-length rule above the body is skipped, which is the behaviour you want.
Changing the vector inside the loop has no effect on the iterations
Because the sequence is evaluated once, appending to or shortening x inside for (v in x) does not add or remove iterations. If the number of iterations depends on what the loop discovers, use while or repeat with break.
The condition in an if inside the loop errors
The reference requires the condition to be “a length-one logical vector that is not NA”. A comparison that returns NA, or a vector of several values, stops the loop with an error; handle missing values before the test.
Only the last result is available
If you printed inside the loop but cannot reuse the values, the loop did not store them. Return to step 3 and assign into a container indexed by position.
A loop returned a list when you wanted a vector
That is usually an apply-family question, covered next: lapply always returns a list.
When to use a for loop and when to use lapply, sapply or vapply
The “Apply Functions Over List or Vector” reference describes three functions that replace the loop-plus-container pattern. They differ in what they return.
| Function | Signature | What it returns, per the R reference |
|---|---|---|
for loop |
for(var in seq) expr |
NULL invisibly; you store results yourself |
lapply |
lapply(X, FUN, ...) |
“a list of the same length as X” |
sapply |
sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE) |
A “user-friendly version and wrapper of lapply”, returning a vector or matrix by default; simplification happens only if X has length greater than 0 and all results share the same positive length, otherwise a list |
vapply |
vapply(X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE) |
Similar to sapply but with a pre-specified return type; each result must match FUN.VALUE in length and type |
A practical reading of that table: use lapply when each step returns something of its own shape (a model, a data frame). Use vapply when you want a plain vector and want an error, not a silent change of shape, if a step returns the wrong kind of value. Use a for loop when steps depend on earlier ones, or when you need break and next.
Free alternatives
Everything below ships with base R, which is free to download from CRAN.
| Construct | Use it for | Source |
|---|---|---|
for |
Repeating an expression once per element of a vector, list or pairlist | R reference, “Control Flow” |
while and repeat |
Repeating until a condition fails or until break; like for, they return NULL invisibly |
R reference, “Control Flow” |
lapply |
Applying a function to each element and keeping a list | R reference, “Apply Functions Over List or Vector” |
sapply |
The same, simplified to a vector or matrix when shapes agree | R reference, “Apply Functions Over List or Vector” |
vapply |
The same, with a declared result type that is checked | R reference, “Apply Functions Over List or Vector” |
FAQ
What is looping in R programming?
Looping is running the same code repeatedly. R’s looping constructs are for, while and repeat, with break and next to control them; the apply family (lapply, sapply, vapply) expresses the same idea as a function call.
What is the difference between for, while and repeat?
for visits each element of a fixed sequence. while keeps going while a condition holds. repeat loops until a break is executed. All three return NULL invisibly.
Should I use a for loop or sapply?
If each iteration produces one value and the iterations are independent, sapply or vapply states the intent in one call and returns the result directly. If iterations depend on each other, or you need break, use a for loop. The decision table above summarises the return types.
Why does my for loop return NULL?
Because for always does. Store values in a container inside the body.
How do I loop over a list in R?
for (item in my_list) works because seq may be a list; lapply(my_list, FUN) does the same and collects the results into a list. For other R analysis guides, see the Kruskal-Wallis test in R and the Bonferroni correction in R.
Sources
- R Core Team, “Control Flow” (base::Control), R reference manual: search.r-project.org/R/refmans/base/html/Control.html. Checked 2026-10-29.
- R Core Team, “Apply Functions Over List or Vector” (base::lapply), R reference manual: stat.ethz.ch/R-manual/R-devel/library/base/html/lapply.html. Checked 2026-10-29.
- R Core Team, An Introduction to R, R 4.6.1, section 9 “Grouping, loops and conditional execution”: cran.r-project.org/doc/manuals/r-release/R-intro.html. Checked 2026-10-29.








