on.exit

Updated March 25, 2026 · Base Functions
r error-handling cleanup functions

on.exit() registers an expression that R evaluates when the enclosing function exits, regardless of whether it returns normally, hits an early return(), or throws an error. This makes it the standard way to handle cleanup in R functions — you register what needs to happen once, and R handles it on the way out.

Signature

on.exit(expr, add = FALSE, which = NULL)

Parameters

ParameterTypeDefaultDescription
exprexpressionAn expression evaluated when the current function exits
addlogicalFALSEIf FALSE, replaces any existing exit expression; if TRUE, appends to the list
whichintegerNULLR ≥ 3.5.0 only. Specifies which existing exit handler to remove

The return value is an integer representing the exit handler, returned invisibly. You rarely need to use it.

Basic Usage

The simplest pattern captures and restores some state:

quiet_mode <- function(expr) {
  old_scipen <- options("scipen")$scipen
  on.exit(options(scipen = old_scipen), add = TRUE)
  options(scipen = 999)
  eval(substitute(expr))
}

quiet_mode(print(1e10))
# [1] 10000000000
options("scipen")$scipen
# [1] 0

By default, on.exit() replaces any previously registered expression:

f <- function() {
  on.exit(cat("first\n"))
  on.exit(cat("second\n"))
  cat("body\n")
}
f()
# body
# second

The first handler is gone. Use add = TRUE to append instead:

f <- function() {
  on.exit(cat("first\n"), add = TRUE)
  on.exit(cat("second\n"), add = TRUE)
  cat("body\n")
}
f()
# body
# second
# first

Exit handlers run in reverse order of registration — last in, first out (LIFO).

Lazy Evaluation

The registered expression is stored as a promise. Variables are looked up at exit time, not registration time:

x <- 10
f <- function() {
  on.exit(print(x))
  x <- 20
  cat("body, x =", x, "\n")
}
f()
# body, x = 20
# [1] 20

If you need to capture a value at registration time, force evaluation explicitly:

f <- function() {
  snapshot <- x      # force evaluation now
  on.exit(print(snapshot))
  x <- 20
}
f()
# [1] 10

Common Use Cases

Reset options()

wider_print <- function(df) {
  old_digits <- options("digits")$digits
  on.exit(options(digits = old_digits), add = TRUE)
  options(digits = 15)
  print(df)
}

wider_print(head(mtcars))
#                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
# Mazda RX4         21.0   6  160.0 110 3.90 2.620 16.46  0  1    4    4
# Mazda RX4 Datsun  21.0   6  160.0 110 3.90 2.875 17.02  0  1    4    4
options("digits")$digits
# [1] 7

Close connections

count_lines <- function(path) {
  conn <- file(path, "r")
  on.exit(close(conn), add = TRUE)
  length(readLines(conn))
}

count_lines("/etc/hosts")
# [1] 10
# connection is closed automatically

Restore graphical parameters

plot_wide <- function() {
  old_mar <- par("mar")
  on.exit(par(mar = old_mar), add = TRUE)
  par(mar = c(2, 2, 2, 2))
  plot(1:10)
  # margins restored on exit
}

Clean up temp files

process_file <- function(url) {
  tmp <- tempfile()
  on.exit(unlink(tmp), add = TRUE)
  download.file(url, tmp, quiet = TRUE)
  read.csv(tmp)
  # temp file deleted regardless of success or failure
}

Interaction with tryCatch

on.exit() runs inside tryCatch() — it fires whether the body succeeds or throws an error:

f <- function() {
  on.exit(cat("cleanup\n"))
  tryCatch({
    cat("trying\n")
    stop("oops")
  }, error = function(e) {
    cat("caught:", conditionMessage(e), "\n")
  })
  cat("after tryCatch\n")
}
f()
# trying
# caught: oops
# cleanup
# after tryCatch

R 4.0.0 added a finally argument to tryCatch() that serves a similar purpose:

f <- function() {
  tryCatch({
    cat("body\n")
  }, finally = {
    cat("cleanup via finally\n")
  })
}
f()
# body
# cleanup via finally

Both on.exit() and finally handle cleanup, but on.exit() is idiomatic for per-function state management while finally is useful for block-level operations.

which Argument (R ≥ 3.5.0)

The which argument lets you remove specific exit handlers by their index:

f <- function() {
  h1 <- on.exit(cat("first\n"), add = TRUE)
  h2 <- on.exit(cat("second\n"), add = TRUE)
  on.exit(which = h1)
  cat("body\n")
}
f()
# body
# second

See Also

  • tryCatch() — R’s full error handling mechanism
  • cat() — output objects to the console (often used in exit handlers)