Write a function

Author

Aurélien Ginolhac

Published

April 3, 2026

NoteObjective

Write a function with multiple arguments, understand for loops, and practice defensive programming and vectorization.

ImportantReport AI usage!

If you use any AI tool, report its name and associated prompts next to your answers.

Any obvious AI answers un-flagged as such will not be considered.

Take 5 min to watch this video by Hadley Wickham, until 5 min.

Being lazy, instead of you doing the work, rely on the work of someone else | Hadley Wickham


Part 0 — Predict before you run

Before writing any function, make sure you can read R code. For each snippet below, write what you expect the output to be in the text block provided, then run the code to check. If you were wrong, explain why in 1–2 sentences.

What is the difference between these two snippets? Predict both outputs, then run.
for (i in c(10, 20, 30)) {
  print(i * 2)
}
for (i in seq_along(c(10, 20, 30))) {
  print(i * 2)
}

My prediction for snippet A:

Write here

My prediction for snippet B:

Write here

What is the key difference between the two, and when would each be useful?

Write here

This code has a bug. Do not run it yet. Identify the bug by reading alone, explain what it does wrong, then fix and run.
NoteNote

There are two problems

is_even_broken <- function(x) {
  result <- logical(length = length(x))
  for (i in seq_along(x)) {
    if (x[i] %% 2 = 0) {
      result[i] <- FALSE
    } else {
      result[i] <- TRUE
    }
  }
  result
}

is_even_broken(c(2, 4, 5, 9))
is_even_broken <- function(x) {
  result <- logical(length = length(x))
  for (i in seq_along(x)) {
    if (x[i] %% 2 == 0) {
      result[i] <- TRUE
    } else {
      result[i] <- FALSE
    }
  }
  result
}

is_even_broken(c(2, 4, 5, 9))
[1]  TRUE  TRUE FALSE FALSE

Aim: write a function is_divisible(x, n) that takes a numeric vector x and a single integer n, and returns a logical vector indicating whether each element of x is divisible by n.

For example:

is_divisible(c(9, 4, 6, 7), n = 3)
# Expected: TRUE FALSE TRUE FALSE

is_divisible(c(10, 15, 7), n = 5)
# Expected: TRUE TRUE FALSE

Note that is_divisible has two arguments. Think about what each one represents.

Part 1 — Pseudocode

Before writing any R code, describe the algorithm in plain English or bullet points. Your pseudocode should be detailed enough that someone who does not know R could follow it.

Function has two arguments: x the vector of number and n a single number.

  • Allocate a result vector of type logical, same size as x
  • For each element of x:
  • test if the modulo (remainder of division) of x by n is equal to zero
  • If yes, fill in the corresponding result with TRUE
  • If no, fill in with FALSE
  • return the vector result

Part 2 — Build it step by step

Version 0: no function yet

Pick a fixed value of n (e.g. 4) and a fixed vector, of length 4 with a mix of even and odd numbers. Write code that checks divisibility without putting it inside a function. Follow your pseudocode.

TipTip

Fill in the table below by hand (without running code) for the input x <- c(6, 5, 12, 7) and n <- 4.

Iteration i x[i] x[i] %% n result after this step
1 1
2 5 FALSE c(FALSE, FALSE)
3 3
4

Then to check, you can add in the for loop a print statement such as:

print(paste("i: ", i, ", x[i]", x[i], ", result[i]: ", result[i]))
x <- c(6, 5, 12, 7)
n <- 4

result <- logical(length = length(x))

for (i in seq_along(x)) {
  if (x[i] %% n == 0) {
    result[i] <- TRUE
  } else {
    result[i] <- FALSE
  }
  print(paste("i: ", i, ", x[i]", x[i], ", result[i]: ", result[i]))
}
[1] "i:  1 , x[i] 6 , result[i]:  FALSE"
[1] "i:  2 , x[i] 5 , result[i]:  FALSE"
[1] "i:  3 , x[i] 12 , result[i]:  TRUE"
[1] "i:  4 , x[i] 7 , result[i]:  FALSE"
Wrap it in a function

Convert your Version 0 code into is_divisible(x, n).

TipTip

Remember that the last command in a function is returned.

Test it on at least three different inputs, including one where all elements are divisible and one where none are.

x <- c(6, 5, 12, 7)
n <- 4


is_divisible <- function(x, n) {
  result <- logical(length = length(x))
  
  for (i in seq_along(x)) {
    if (x[i] %% n == 0) {
      result[i] <- TRUE
    } else {
      result[i] <- FALSE
    }
    #print(paste("i: ", i, ", x[i]", x[i], ", result[i]: ", result[i]))
  }
  result
}

is_divisible(x, n = 4)  #  c(6, 5, 12, 7)
[1] FALSE FALSE  TRUE FALSE
is_divisible(x = c(3, 6, 7), n = 4) 
[1] FALSE FALSE FALSE
is_divisible(x = c(3, 6, 9), n = 3) 
[1] TRUE TRUE TRUE

Part 3 — Including defensive programming

What happens if a user calls is_divisible(1:3, 3:4), is_divisible("hello", 3) or is_divisible(c(1, 2, 3), "a")?

Try it and describe what R does:
# try bad inputs here and observe the errors
is_divisible(1:3, 3:4) # the divider is not a single value
Error in `if (x[i]%%n == 0) ...`:
! the condition has length > 1
is_divisible("hello", 3) # characters are not numbers
Error in `x[i] %% n`:
! non-numeric argument to binary operator
is_divisible(c(1, 2, 3), "a") # same
Error in `x[i] %% n`:
! non-numeric argument to binary operator
Write test at the beginning of the function to catch those errors
  1. x not being numeric
  2. n not being a single integer (hint: length(n) != 1)
TipTip

The R code:

stop("Warning! some conditions are not fulfilled to continue bla bla \n")

should help. It stops the execution and print the chosen message. To prevent a chunk to stop the rendering with a desired error, use the option

#| error: true
is_divisible <- function(x, n) {
  
  if (length(n) != 1L) stop("divider n is not a single value ")
  if (!is.numeric(n)) stop(paste("divider n must be a numeric ", n))
  if (!is.numeric(x)) stop(paste("x must be numeric ", n))
  
  result <- logical(length = length(x))
  
  for (i in seq_along(x)) {
    if (x[i] %% n == 0) {
      result[i] <- TRUE
    } else {
      result[i] <- FALSE
    }
    #print(paste("i: ", i, ", x[i]", x[i], ", result[i]: ", result[i]))
  }
  result
}


is_divisible(1:3, 3:4) # the divider is not a single value
Error in `is_divisible()`:
! divider n is not a single value 
is_divisible("hello", 3) # characters are not numbers
Error in `is_divisible()`:
! x must be numeric  3
is_divisible(c(1, 2, 3), "a") # same
Error in `is_divisible()`:
! divider n must be a numeric  a

Part 4 — Vectorization

Now that the for loop was a pain to set-up, we can use what is good at: vectorization

Replace all the for loop code with one line of vectorization

The result vector is not needed.

is_divisible <- function(x, n) {
  
  if (length(n) != 1L) stop("divider n is not a single value ")
  if (!is.numeric(n)) stop(paste("divider n must be an integer ", n))
  if (!is.numeric(x)) stop(paste("x must be numeric ", n))
  
  x %% n == 0
}

is_divisible(x, n)
[1] FALSE FALSE  TRUE FALSE

Bonus questions

Optional questions. You can wrap the new questions around the already written is_divisible() to benefit from the defensive programming already written.

Create a new function that returns the number of divisible items

Such as:

count_divisible(c(4, 8, 12), n = 4)
[1] 3
count_divisible <- function(x, n) {
  sum(is_divisible(x, n))
}
count_divisible(x, n)
[1] 1
count_divisible(c(4, 8, 12), n)
[1] 3
Create another function so it returns the sum of even items

Such as:

sum_divisible(c(4, 8, 12), n = 4)
[1] 24
sum_divisible <- function(x, n) {
  sum(x[is_divisible(x, n)])
}
sum_divisible(x, n)
[1] 12
sum_divisible(c(4, 8, 12), n)
[1] 24