R Control Flow: if/else, for, and while — Stop Avoiding Loops
Control flow lets your code make decisions and repeat actions. R provides if/else for branching, for/while for looping, and vectorized alternatives like ifelse() and sapply() that often replace loops entirely.
You'll hear people say "never use loops in R." That's an oversimplification. Loops are perfectly fine — R just gives you powerful alternatives that are often cleaner. This tutorial teaches you all the control flow tools, when to use each one, and when a vectorized solution is genuinely better.
Introduction
Control flow determines which code runs and how many times. Without it, code runs top to bottom, one line at a time. Control flow adds two capabilities:
- Choices — run different code depending on a condition (if/else)
- Repetition — run the same code multiple times (for, while)
Here's what we'll cover:
if,else if,else— make decisionsifelse()— vectorized decisions (apply to whole vectors)switch()— choose from multiple optionsfor— repeat a known number of timeswhile— repeat until a condition changesnextandbreak— control loop flow- When to use loops vs vectorized solutions
if / else: Making Decisions
The if statement runs code only when a condition is TRUE:
The second if block doesn't run because 35 is not less than 0. The code after the if blocks always runs.
if / else
When you want to do one thing OR another:
if / else if / else
For multiple conditions, chain them with else if:
R checks conditions top to bottom and stops at the first TRUE condition. Order matters — put the most specific conditions first.
Common if/else patterns
Important: The condition inside
if()must be a single TRUE or FALSE value, not a vector. If you pass a vector, R uses only the first element and warns you. For vectorized conditions, useifelse()(covered next).
ifelse(): Vectorized Decisions
ifelse() applies a condition to every element of a vector — no loop needed:
Compare this to a loop version:
ifelse() is cleaner and faster. Use it whenever you're applying a condition to a vector.
Nested ifelse for multiple conditions
Nested ifelse() gets hard to read. For many conditions, consider dplyr::case_when() instead:
Much more readable. case_when() is the modern R way to handle multiple vectorized conditions.
switch(): Choose from Named Options
switch() picks one option based on a string value — cleaner than long if/else chains:
switch() is especially useful in functions where an argument selects a method or mode.
for Loops: Repeat a Known Number of Times
A for loop iterates over each element in a sequence:
Looping over vectors
Tip: Use
seq_along(x)instead of1:length(x). Ifxis empty,1:length(x)givesc(1, 0)which causes bugs.seq_along(x)correctly gives an empty sequence.
Building results in a loop
Critical: Always pre-allocate your result vector with numeric(n), character(n), etc. Growing a vector inside a loop (result <- c(result, new_value)) is extremely slow in R because it copies the entire vector each time.
Nested loops
Looping over data frame rows
Storing results in a list
When each iteration produces a complex result (not just a single value), use a list:
while Loops: Repeat Until a Condition Changes
A while loop keeps running as long as a condition is TRUE:
Warning: Always make sure the condition will eventually become FALSE. A while(TRUE) without a break creates an infinite loop that locks up your R session.
Real use case: iterative convergence
while loops are best for situations where you don't know in advance how many iterations you need.
break and next: Loop Control
break — exit the loop early
next — skip to the next iteration
Loops vs Vectorized: When to Use Which
R's vectorized operations are usually better than loops for element-wise operations. Here's a decision guide:
| Situation | Use | Why |
|---|---|---|
| Apply math to a vector | Vectorized (x * 2) |
Faster, cleaner |
| Filter/subset | Vectorized (x[x > 0]) |
One line vs many |
| Conditional on each element | ifelse() or case_when() |
Vectorized, readable |
| Apply function to each element | sapply()/lapply() |
Cleaner than loop |
| Iterations depend on previous result | for/while loop |
Can't vectorize |
| Side effects (print, write files) | for loop |
Clear intent |
| Complex multi-step per iteration | for loop |
Easier to debug |
| Unknown number of iterations | while loop |
Convergence, search |
The vectorized version is typically 100-300x faster for simple math. But for complex logic, a well-written loop is perfectly acceptable.
Practice Exercises
Exercise 1: FizzBuzz
Click to reveal solution
Explanation: Check %% 15 first (divisible by both 3 and 5) because if you check %% 3 first, multiples of 15 would hit that condition and never reach the FizzBuzz check. Order matters in if/else chains.
Exercise 2: Grade Calculator with case_when
Click to reveal solution
Explanation: pmin(scores + 5, 100) adds 5 to each score and caps at 100 (parallel minimum). case_when() vectorizes the grade assignment. table() counts occurrences.
Exercise 3: Simulation Loop
Click to reveal solution
Explanation: A while loop is perfect here — we don't know how many rounds the game will take. sample(c("H","T"), 1) simulates a fair coin. The history vector lets us plot the balance over time.
Summary
| Tool | Syntax | Use when |
|---|---|---|
if/else |
if (cond) {} else {} |
Single condition, one decision |
else if |
else if (cond) {} |
Multiple exclusive conditions |
ifelse() |
ifelse(vec, yes, no) |
Apply condition to whole vector |
case_when() |
case_when(cond ~ val) |
Multiple vectorized conditions |
switch() |
switch(x, "a"=1, "b"=2) |
Choose by string value |
for |
for (i in seq) {} |
Known iterations, side effects |
while |
while (cond) {} |
Unknown iterations, convergence |
break |
break |
Exit loop early |
next |
next |
Skip current iteration |
Golden rule: If you're applying the same operation to every element of a vector, try a vectorized solution first. If the logic is complex, iterative, or involves side effects, use a loop.
FAQ
Are R loops really that slow?
R loops are slower than vectorized operations for simple math, but they're not slow in absolute terms. A loop over 10,000 iterations takes milliseconds. Loops only become a problem with millions of iterations doing simple operations — and those are exactly the cases where vectorization shines.
What's the difference between for and while?
for loops iterate over a predefined sequence — you know in advance how many times the loop will run. while loops keep going until a condition changes — you don't know in advance how many iterations you'll need. Use for when you can; use while for convergence, searching, or games.
Can I use for loops with data frames?
Yes. Loop over rows with for (i in 1:nrow(df)), or over columns with for (col in names(df)). But for most data frame operations, dplyr functions (filter, mutate, summarise) are cleaner than loops.
What does seq_along() do?
seq_along(x) generates the sequence 1, 2, 3, ..., length(x). It's safer than 1:length(x) because when x is empty, 1:length(x) gives c(1, 0) (which causes bugs), while seq_along(x) correctly gives an empty sequence.
How do I avoid infinite while loops?
Three safeguards: (1) Make sure the condition can become FALSE — something inside the loop must change the variables in the condition. (2) Add a maximum iteration counter. (3) Use break as an escape hatch. When in doubt, start with a for loop with a maximum count.
What's Next?
Now that you can make decisions and repeat actions, you're ready to encapsulate logic into reusable pieces:
- Writing R Functions — create your own functions with arguments, defaults, and return values
- R Special Values — handle NA, NULL, NaN, and Inf properly in your control flow
- Getting Help in R — navigate R's documentation system efficiently
Each tutorial builds on the control flow patterns you've learned here.