Beginner

Control Flow in R

Master conditionals, loops, and vectorized control flow in R with practical Docker-ready examples for statistical computing

Control flow determines the order in which your code executes and lets your programs make decisions and repeat work. R provides all the familiar building blocks—if/else conditionals, for and while loops, and a switch statement—but it approaches control flow from the perspective of a language built for statistical computing.

R is a multi-paradigm language (Array, Object-Oriented, Functional, Procedural, and Reflective) with dynamic, strong typing. Because R was designed to work with vectors and data frames, the most idiomatic “control flow” often isn’t a loop at all—it’s a vectorized operation that applies a decision to an entire vector at once. A subtle but important detail: in R, if is an expression that returns a value, so conditionals can be assigned directly to variables.

In this tutorial you’ll learn how to write conditionals with if/else if/else, make vectorized decisions with ifelse(), branch on values with switch(), iterate with for, while, and repeat loops, and control loop execution with break and next. Just as importantly, you’ll learn when to avoid explicit loops in favor of R’s vectorized approach.

Conditionals: if, else if, else

The if statement evaluates a condition and runs a block only when that condition is TRUE. You can chain conditions with else if and provide a fallback with else.

Create a file named conditionals.R:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Conditionals in R
temperature <- 18

if (temperature > 25) {
  print("It's hot")
} else if (temperature >= 15) {
  print("It's mild")
} else {
  print("It's cold")
}

# In R, if is an expression - it returns a value you can assign
status <- if (temperature >= 15) "comfortable" else "chilly"
print(paste("Status:", status))

The condition inside if (...) must evaluate to a single logical value (TRUE or FALSE). Note the placement of else: it must appear on the same line as the closing brace } so R knows the statement continues. The final two lines show R’s distinctive feature—if returns a value, so the whole conditional can be assigned to status in one expression.

Vectorized Conditionals and switch

A plain if only handles a single value. When you need to apply a condition to an entire vector—a common situation in data analysis—use ifelse(), which evaluates the condition element by element and returns a vector. To branch on a single discrete value, switch() is cleaner than a long if/else if chain.

Create a file named vectorized_conditions.R:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Vectorized conditional with ifelse()
scores <- c(95, 62, 78, 40, 88)
results <- ifelse(scores >= 60, "Pass", "Fail")
print(results)

# switch() selects a branch based on a single value
grade_label <- function(letter) {
  switch(letter,
    A = "Excellent",
    B = "Good",
    C = "Average",
    "Unknown")  # the unnamed final argument is the default case
}

print(grade_label("A"))
print(grade_label("Z"))

ifelse(test, yes, no) returns the yes value wherever test is TRUE and the no value wherever it’s FALSE, preserving the shape of the input vector—a single call replaces an entire loop. The switch() function matches its first argument against the named cases; an unnamed final argument acts as the default when nothing matches, which is why grade_label("Z") returns "Unknown".

Loops: for, while, and repeat

R offers three loop constructs. A for loop iterates over the elements of a vector (or any sequence), a while loop runs as long as a condition holds, and repeat loops forever until you explicitly break. Inside any loop, next skips to the next iteration and break exits the loop entirely.

Create a file named loops.R:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# for loop iterates over the elements of a vector
for (i in 1:5) {
  cat("Square of ", i, " is ", i^2, "\n", sep = "")
}

# while loop runs while its condition is TRUE
countdown <- 3
while (countdown > 0) {
  cat("Countdown: ", countdown, "\n", sep = "")
  countdown <- countdown - 1
}

# repeat loops until break; next skips an iteration
total <- 0
n <- 0
repeat {
  n <- n + 1
  if (n == 3) next   # skip adding 3
  if (n > 5) break   # stop after 5
  total <- total + n
}
cat("Sum (excluding 3): ", total, "\n", sep = "")

The for loop walks through 1:5, the integer sequence from 1 to 5. The while loop decrements countdown until the condition becomes FALSE. The repeat block has no built-in stopping condition, so break is mandatory—here next skips the iteration where n is 3, and break ends the loop once n exceeds 5, giving a sum of 1 + 2 + 4 + 5 = 12.

The R Way: Vectorize Instead of Looping

Because R is fundamentally an array language, explicit loops are often slower and less readable than operating on whole vectors at once. Many tasks that would require a loop in other languages become a single expression in R. Reaching for vectorized operations first is the idiomatic approach.

Create a file named vectorize.R:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Operate on an entire vector without an explicit loop
numbers <- 1:10

# Square every element at once
squares <- numbers^2
print(paste(squares, collapse = " "))

# Keep only elements that satisfy a condition (logical subsetting)
evens <- numbers[numbers %% 2 == 0]
print(paste("Evens:", paste(evens, collapse = " ")))

# Reduce a whole vector to a single value - no accumulator loop
print(paste("Sum:", sum(numbers)))

numbers^2 squares all ten elements in one operation—no loop needed. The expression numbers[numbers %% 2 == 0] uses logical subsetting: numbers %% 2 == 0 produces a logical vector that selects only the even values. Functions like sum() collapse an entire vector into a single result, replacing the accumulator pattern you’d write with a loop. This vectorized style is both faster and closer to how R is meant to be used.

Running with Docker

Docker lets you run these examples in a consistent R 4.4.2 environment without installing R locally.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the official image
docker pull r-base:4.4.2

# Run the conditionals example
docker run --rm -v $(pwd):/app -w /app r-base:4.4.2 Rscript conditionals.R

# Run the vectorized conditionals example
docker run --rm -v $(pwd):/app -w /app r-base:4.4.2 Rscript vectorized_conditions.R

# Run the loops example
docker run --rm -v $(pwd):/app -w /app r-base:4.4.2 Rscript loops.R

# Run the vectorization example
docker run --rm -v $(pwd):/app -w /app r-base:4.4.2 Rscript vectorize.R

Expected Output

Running conditionals.R:

[1] "It's mild"
[1] "Status: comfortable"

Running vectorized_conditions.R:

[1] "Pass" "Pass" "Pass" "Fail" "Pass"
[1] "Excellent"
[1] "Unknown"

Running loops.R:

Square of 1 is 1
Square of 2 is 4
Square of 3 is 9
Square of 4 is 16
Square of 5 is 25
Countdown: 3
Countdown: 2
Countdown: 1
Sum (excluding 3): 12

Running vectorize.R:

[1] "1 4 9 16 25 36 49 64 81 100"
[1] "Evens: 2 4 6 8 10"
[1] "Sum: 55"

Key Concepts

  • if is an expression: Unlike many languages, R’s if/else returns a value, so you can assign a conditional directly to a variable (e.g., status <- if (x > 0) "pos" else "neg").
  • else placement matters: In scripts, else must sit on the same line as the closing } of the preceding block, or R treats the if as complete.
  • Use ifelse() for vectors: A scalar if cannot test an entire vector. ifelse(test, yes, no) applies the condition element-wise and returns a vector of the same length.
  • switch() for value-based branching: Cleaner than long if/else if chains; the unnamed final argument provides a default case.
  • Three loop forms: for iterates over a sequence, while runs while a condition holds, and repeat loops until an explicit break.
  • break and next: break exits a loop entirely; next skips to the next iteration—essential for controlling repeat and filtering iterations.
  • Prefer vectorization: Operating on whole vectors (x^2, sum(x), logical subsetting like x[x > 0]) is faster and more idiomatic in R than writing explicit loops.

Running Today

All examples can be run using Docker:

docker pull r-base:4.4.2
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining