The Basic for Loop
A for loop in R runs a block of code once for each element of a vector or list. The loop variable takes each value in turn:
Three elements, three passes. There's no counter to set up and no condition to maintain - for (fruit in fruits) reads as "for each fruit in fruits", and that's exactly what it does. The same shape iterates over a list's elements, a sequence of numbers (for (i in 1:10)), or the names of anything via names().
Printing Inside a Loop Needs print() or cat()
At the console, typing x prints its value. Inside a loop body, it doesn't - auto-printing only happens at the top level, and a bare expression inside a loop is evaluated and thrown away:
So when a loop "does nothing", this is the first thing to check. Use print() for a quick look at any value, or cat() when you're composing a line of output yourself (remember the "\n" - cat() doesn't add one).
Looping Over Indices with seq_along()
Sometimes you need the position as well as the value - to write into another vector at the same index, or to print a numbered list. Loop over indices with seq_along():
You will see for (i in 1:length(prices)) in older code, and it harbors a genuine bug. When the vector is empty, length() is 0, and 1:0 doesn't mean "no iterations" - the colon counts down:
A loop over 1:length(empty) runs twice, indexing elements that don't exist (empty[1] is NA, and assignments create phantom entries). seq_along() returns an empty sequence for an empty vector, so the loop body simply never runs. Make seq_along() the habit; its sibling seq_len(n) does the same job when you have a count rather than a vector.
Collecting Results: Preallocate, Don't Grow
The natural first instinct for building up results looks like this:
squares <- c()
for (i in 1:10000) {
squares <- c(squares, i^2) # copies the ENTIRE vector every pass
}
It works, but every c(squares, ...) allocates a brand-new vector and copies all the old elements into it. By the end you've copied roughly 50 million numbers to produce 10,000. This growing-vector anti-pattern is the single biggest reason people call R loops slow.
The fix: create the result at full size first, then assign by index:
For results that aren't numbers, the same idea applies with character(n), logical(n), or vector("list", n) for a list of arbitrary things. Preallocated loops are perfectly fast.
next and break
Two keywords steer a loop from inside its body: next abandons the current pass and jumps to the next element; break exits the loop entirely.
This prints 2 4 6 8: odd values are skipped by next, and when 10 arrives, break ends the loop before printing. Use next to filter out cases early (it keeps the main body un-indented), and break when a loop has found what it was looking for and has no reason to continue.
Nested Loops
A loop body can contain another loop. The inner loop runs to completion for every single pass of the outer one - the classic demonstration is a multiplication table:
For each row i, the inner loop walks all columns j, and the row's newline prints after the inner loop finishes. Nested loops are fine at two levels; at three or more, the body is usually asking to become a function, or the whole computation is asking to be an outer() or matrix operation.
Nesting also covers looping through a list of vectors - outer loop over the list, inner loop (or better, a vectorized call) over each element:
Note there's no inner loop after all - mean() handles the whole inner vector in one call. That observation generalizes, which brings us to the honest part.
When Not to Loop
R is a vectorized language: its basic operations already work on whole vectors at once. A loop that transforms each element one at a time is often a longer way to write a one-liner:
Prefer the vectorized form whenever one exists - it's shorter, harder to get wrong, and faster. For applying a function across every element of a list, the apply family (sapply, lapply, vapply) plays the same role.
But don't cargo-cult this into "loops are bad." A loop is the right tool when iterations depend on previous ones (running state, simulations), when you're doing side effects like writing files, or simply when the loop is the version you and your readers understand at a glance. A clear loop beats a clever one-liner nobody can parse.
What You Take Away
for (x in v) { ... }runs the body once per element - no counter bookkeeping.- Inside a loop, print explicitly with
print()orcat(); bare expressions are discarded. - Loop over positions with
seq_along(v), never1:length(v)- the latter runs twice on an empty vector. - Preallocate results with
numeric(n)/vector("list", n)and assign by index; growing withc()copies everything every pass. nextskips a pass,breakexits; prefer vectorized operations when they exist, but don't fear a readable loop.
Next up: loops that run until something happens rather than once per element - while and repeat.
Frequently Asked Questions
How do you write a for loop in R?
for (x in v) { ... } - the loop variable x takes each element of the vector (or list) v in turn, and the body runs once per element. To loop over positions instead of values, use for (i in seq_along(v)) and index with v[i].
Why use seq_along instead of 1:length(v) in R?
When v is empty, length(v) is 0, so 1:length(v) becomes 1:0 - the two-element vector c(1, 0) - and the loop body runs twice against elements that don't exist. seq_along(v) returns an empty sequence for an empty vector, so the loop correctly runs zero times.
Why is my for loop in R not printing anything?
R's auto-printing only happens for expressions typed at the console top level. Inside a loop body, a bare expression like x is evaluated and discarded. Wrap it in print(x), or use cat(...) when you want to format the output yourself.
Are for loops slow in R?
The loop itself is fine - what's slow is growing a vector inside one with result <- c(result, ...), which copies the whole vector on every pass. Preallocate with numeric(n) or vector("list", n) and assign by index, and a loop performs perfectly well. That said, when a vectorized operation or an apply-family call expresses the same idea, it's usually both faster and clearer.