23  Loops in R: For, While, and Repeat

NoteWhat this chapter covers

A loop runs the same block of code many times. R offers three forms, for, while, and repeat, each suited to a different rhythm of repetition. This chapter explains when to use each, the two control keywords (break and next) that interrupt the rhythm, why pre-allocating output matters, and the cultural pressure in R to prefer vectorised operations or the apply family over hand-written loops. By the end you will write loops confidently, but only when they are the right tool.

23.1 When to use a loop

A loop is the right tool when:

  • you need to repeat an action a fixed number of times,
  • you need to keep going until something happens,
  • each step depends on the result of the previous step (so vectorisation is impossible).

A loop is the wrong tool when a vectorised operation (x + 1, sum(x), ifelse(…)) or an apply function does the same job in one line. Most R beginners write loops for tasks R already solves vectorwise, try the one-liner first.

23.2 for, known number of iterations

for walks through every element of a vector or list, binding each to a variable in turn.

for (var in sequence) {
  # body - runs once per element
}

The loop variable can take any name and any value, integer, character, list element.

When you need both the value and its position, loop over the indices with seq_along():

WarningWhy seq_along(x) and not 1:length(x)

If x happens to be empty, length(x) is 0 and 1:0 is c(1, 0), so the loop runs twice on an empty input and crashes. seq_along(x) returns an empty integer when x is empty, so the loop simply doesn’t execute. Use seq_along() (and seq_len(n)) by default.

23.3 while, keep going until a condition fails

while checks a condition before each iteration and stops as soon as it becomes FALSE.

while (condition) {
  # body
}

while is right when you don’t know the iteration count in advance, convergence loops, “keep retrying”, “drain a queue”.

WarningInfinite loops

If the condition never becomes FALSE, the loop never stops. Always make sure the loop body changes something the condition depends on. In an interactive console, press the stop button (or Esc / Ctrl-C) to interrupt a runaway loop.

23.4 repeat, loop forever, exit with break

repeat has no condition at all. The body runs forever unless an explicit break interrupts it. Useful when the natural exit point sits in the middle of the body, not at the top.

repeat is the loop you reach for least often, almost any repeat can be rewritten as a while with a clearer condition. Reserve it for cases where the exit test reads more naturally inside the body.

23.5 break and next

Two keywords steer execution from inside a loop:

  • break jumps out of the loop entirely.
  • next skips the rest of the current iteration and moves on to the next one.

next is R’s equivalent of continue in C-family languages. Both keywords apply only to the innermost enclosing loop.

23.6 Building output, pre-allocate

A common loop pattern collects results into a vector. The slow way is to grow the output one element at a time:

out <- c()
for (i in 1:n) {
  out <- c(out, i^2)        # reallocates the whole vector each step
}

The fast way is to pre-allocate an output of the right size and assign by index:

For small n the difference is invisible. For large n the slow form can be hundreds of times slower because R repeatedly copies the growing vector.

23.7 Nested loops

Loops nest naturally, each level needs its own counter.

Two warnings:

  1. Nested loops multiply work. Three loops of length 1000 means a billion iterations.
  2. break and next only escape one level. To leave several levels at once, use a flag variable or wrap the loops in a function and return().

23.8 Loops vs vectorisation

R is built for vectorised operations, applying an action to a whole vector in one call. The vectorised version is almost always shorter, faster, and clearer.

The pattern repeats throughout R: sum(), mean(), which(), cumsum(), pmax(), comparison operators, arithmetic, all consume and produce whole vectors. Reach for them before reaching for a for loop.

When per-element logic genuinely needs to differ (and you can’t express it vectorwise), the apply family, sapply(), lapply(), vapply(), mapply(), is the next step up. We’ll meet it properly in the next chapter.

TipThree reasons a loop is the right answer
  1. Each iteration depends on the previous one (running totals, simulations).
  2. The work has side effects (writing files, printing progress, updating a database).
  3. The vectorised version would be less readable than the loop.

Don’t apologise for using a loop in those cases, it’s the right tool.

23.9 Worked example, compound interest

Compound interest is a textbook iterative calculation: each year’s balance depends on the previous year’s. We’ll use a for loop because the number of years is known, and we’ll pre-allocate the output to track the trajectory.

Two design choices worth flagging:

  • The output vector is sized years + 1 so we can store the starting balance at index 1 and reach the end at index years + 1.
  • Each step refers to balance[y], the previous step’s value, that genuine dependency is why a vectorised one-liner won’t do. (Though for compound interest, principal * (1 + rate)^(0:years) is also vectorisable, try both and confirm they match.)

True dependency examples, like simulating a stochastic process where each step uses a random draw conditioned on the last, are where the loop genuinely earns its place.

23.10 Summary

Loop Iterates Ends when Typical use
for (v in seq) { … } every element of seq the sequence is exhausted known number of iterations
while (cond) { … } until cond is FALSE the condition fails convergence, retry, drain
repeat { …; break } forever an explicit break exit test in the middle of the body

Two control keywords:

  • break, leave the innermost loop now.
  • next, skip to the next iteration.

Three habits to keep:

  • Use seq_along() and seq_len() instead of 1:n.
  • Pre-allocate the output vector before assigning into it.
  • Try the vectorised form first; only loop when you must.

Loops are essential, but in R they are not the first tool, they are the tool you use after you’ve checked whether vectorised arithmetic, an apply function, or a dplyr verb already does the job in one line. The next chapter shows how to iterate over the richer data structures, lists, matrices, data frames, using both loops and the apply family side by side.