Control Flow

Applying R to Lifestyle and Brain Health Research

Julianne G. Clina, PhD

University of Kansas Medical Center

September 2, 2026

Control Flow

Control flow just refers to the order in which the code is executed.

The primary tools for controlling flow are choices and loops. Choices allow you to run different code depending on the input (e.g., if statements and switch). Loops allow you to repeatedly run code with changing options (e.g., for and while loops).

If, Else, and Else If

A basic if statement will evaluate if some expression is true or false and then execute a specific task if the expression is true.

x <- 10
if (x > 5) {
  print("x is greater than 5")
}
# [1] "x is greater than 5"

y <- 3
if (y > 5) {
  print("y is greater than 5")
}
# [1]

To enhance the if statement, you can add an else condition. This means that the true_action is evaluated if the condition is TRUE and the false_action is evaluated if the condition is FALSE.

if (y > 5) {
  print("y is greater than 5")
} else {
  print("y is less than 5")
}
#[1] "y is less than 5"

Using else if allows you to make a chain of statements where each input is evaluated through this chain of statements. This means that you are giving a list of conditions and seeing which matches your input. The actions are often compound statements contained within { and returns a value so you can assign the results.

grade <- function(x) {
  if (x > 90) {
    "A"
  } else if (x > 80) {
    "B"
  } else if (x > 50) {
    "C"
  } else {
    "F"
  }
}

(c(grade(85), grade(98)))
#> [1] "B" "A"

ifelse and case_when

The choices we just talked about do not work with vectors with multiple values, only single inputs at one time. So what if you want to evaluate a vector with multiple inputs?

Below is an example of ifelse() where all the numbers in the sequence 1 through 12 are being checked to see if they are divisible by 5. If they are, it will print “XXX”, otherwise it will just print the value as a character.

x <- 1:12
ifelse(x %% 5 == 0, "XXX", as.character(x))
#[1] "1"   "2"   "3"   "4"   "XXX" "6"   "7"   "8"   "9"  "XXX" "11"  "12"

And here is an example of case_when() where you are evaluating multiple inputs for multiple conditions.

library(dplyr)
case_when(
  x > 10 ~ "Big number",
  x > 5 ~ "Medium number",
  TRUE ~ "Small number"
)
# [1] "Small number"  "Small number"  "Small number"  Small number"  "Small number"  "Medium number" "Medium number" "Medium number" "Medium number" "Medium number" "Big number"    "Big number"
You want to evaluate… Type of Statement to use
One condition, one value if / else
Multiple conditions, one value if / elseif / else
One condition, multiple values ifelse() (vectorized)
Multiple conditions, multiple values case_when()

Switch Statements

The switch() function in R is a more compact alternative to writing long chains of if, else if, and else conditions when you are comparing a single input to several known options.

Instead of checking conditions one by one, switch() directly matches the input to a predefined list of values and returns the associated result. Compare this example using else if:

x_option <- function(x) {
  if (x == "a") {
    "option 1"
  } else if (x == "b") {
    "option 2"
  } else if (x == "c") {
    "option 3"
  } else {
    print("Invalid `x` value")
  }
}
x_option("a")
# [1] "option 1"
x_option("hand")
# [1] "Invalid `x` value"

With this example using switch

x_option <- function(x) {
  switch(
    x,
    a = "option 1",
    b = "option 2",
    c = "option 3",
    stop(
      "Invalid x value"
    )
  )
}
x_option("a")
# [1] "option 1"
x_option("hand")
# [1] "Invalid x value"

With switch statements, if multiple inputs have the same value, you can leave it blank after the = and the input will fall through to the next value.

legs <- function(x) {
  switch(
    x,
    cow = ,
    horse = ,
    dog = 4,
    human = ,
    chicken = 2,
    plant = 0,
    stop("Unknown input")
  )
}

legs("cow")
# [1] 4
legs("dog")
# [1] 4
legs("goat")
#[1] Error in legs("goat") : Unknown input

Loops

Loops are used when you want to repeat a block of code on multiple inputs and have the following basic form: for (item in vector) perform_action

for (i in 1:3) {
  print(i)
}
#> [1] 1
#> [1] 2
#> [1] 3

weights <- c(60, 72, 85, 90)
height <- 1.7

for (w in weights) {
  bmi <- w / height^2
  print(paste("Weight:", w, "kg -> BMI:", round(bmi, 1)))
}
#>[1] "Weight: 60 kg -> BMI: 20.8"
#>[1] "Weight: 72 kg -> BMI: 24.9"
#>[1] "Weight: 85 kg -> BMI: 29.4"
#>[1] "Weight: 90 kg -> BMI: 31.1"

There are two ways to terminate a for loop early:

  • next exits the current iteration.
  • break exits the entire for loop.

This can be especially useful in large data sets

for (i in 1:10) {
  if (i < 3) {
    next
  }

  print(i)

  if (i >= 5) {
    break
  }
}
#> [1] 3
#> [1] 4
#> [1] 5

Common Loop Pitfalls

There are three common pitfalls to watch out for when using for.

  1. Failing to pre-allocate output containers
  2. Iterating over 1:length(x)
  3. Iterating over S3 vectors

Pitfall 1: Failing to pre-allocate output containers

Make sure to pre-allocate the output container. Otherwise the loop will be very slow. This would be like you decide to put books on a bookshelf at home, but every time you get a new book, you have to go out to the store and buy a new bookshelf that is exactly 1 book larger than your last one.

timer <- function(prealloc, label) {
  set.seed(123)
  daily_avg_steps <- sample(x = 3000:12000, size = 1000000, replace = TRUE)
  tictoc::tic(label)
  for (i in seq_along(daily_avg_steps)) {
    prealloc[[i]] <- rpois(7, lambda = daily_avg_steps[i])
  }
  tictoc::toc()
}

timer(prealloc = list(), label = "Without preallocation")
# Without preallocation: 1.12 sec elapsed

timer(
  prealloc = vector("list", length(daily_avg_steps)),
  label = "With preallocation"
)
# With preallocation: 0.77 sec elapsed

Pitfall 2: Iterating over 1:length(x)

Attempting to iterate over 1:length(x) will fail in unexpected ways if x has a length of 0. If x is 0, then this is read as 1:0 which is a two element vector [1,0].

daily_avg_steps <- c()

for (i in 1:length(daily_avg_steps)) {
  print(daily_avg_steps[i])
}
# NULL
# NULL

for (i in seq_along(daily_avg_steps)) {
  print(daily_avg_steps[i])
}

Pitfall 3: Iterating over S3 vectors

Dates are an example of an S3 class and in R, dates get stored as days since Jan 1 1970. So if you want to run a loop, you need to preserve the class attribute with seq_along() and double brackets [[]]

birthdays <- as.Date(c("1990-05-01", "1985-09-20"))
str(birthdays)
# Date[1:2], format: "1990-05-01" "1985-09-20"
class(birthdays)
#[1] "Date"

for (b in birthdays) {
  print(b)
}
# [1] 7425
# [1] 5741

for (i in seq_along(birthdays)) {
  print(birthdays[[i]])
}
# [1] "1990-05-01"
# [1] "1985-09-20"

While and Repeat

For loops are useful if you know in advance the set of values that you want to iterate over. If you don’t know, there are two related tools with more flexible specifications:

while(condition) action: performs action while condition is TRUE.

repeat(action): repeats action forever (i.e. until it encounters break).

bpcut <- 120
bpsys <- 126

while (bpsys > bpcut) {
  print("Your systolic blood pressure is elevated")
  bpsys <- bpsys - 1
}
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"

bpcut <- 120
bpsys <- 126

repeat {
  if (bpsys <= bpcut) {
    break
  }
  print("Your systolic blood pressure is elevated")
  bpsys <- bpsys - 1
}

# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"
# [1] "Your systolic blood pressure is elevated"

step_goal <- 10000
current_steps <- 6500

repeat {
  if (current_steps >= step_goal) {
    print("Congrats! You've reached your daily step goal.")
    break
  }

  print(paste(
    "You have",
    step_goal - current_steps,
    "steps to go. Keep moving!"
  ))
  steps_taken <- sample(500:1500, 1)
  current_steps <- current_steps + steps_taken

  Sys.sleep(1)
}
# {1] "You have 3500 steps to go. Keep moving!"
# [1] "You have 2901 steps to go. Keep moving!"
# [1] "You have 2131 steps to go. Keep moving!"
# [1] "You have 792 steps to go. Keep moving!"
# [1] "Congrats! You've reached your daily step goal."