Error Handling (tryCatch)

Reviewed & published by Brayan K

Error handling is how you signal problems with stop(), warning(), and message(), and how you catch them with tryCatch() so a failure produces a graceful fallback instead of crashing your program.

Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

You'll learn to raise the three R conditions, catch them with tryCatch error/warning/finally handlers, read the message with conditionMessage(), use the lightweight try(), and validate inputs early with stopifnot().

What You'll Learn in This Lesson

1️⃣ Signalling: stop, warning, message

R has three condition types you can raise yourself. stop() is a fatal error that halts execution. warning() flags something suspicious but lets the code continue. message() prints an informational note. Choosing the right one makes your functions communicate clearly.

2️⃣ Catching with tryCatch()

tryCatch() runs an expression and, if a condition fires, hands it to the matching handler — error =, warning =, or message = — each a function receiving the condition object. conditionMessage() extracts its human-readable text. The handler's return value becomes the result.

3️⃣ The finally Block

A finally = block runs after the expression and any handler, whether the call succeeded or failed. It's where you put guaranteed cleanup — closing a file, releasing a connection, or simply logging that an attempt finished.

# finally = runs no matter what — perfect for cleanup.
read_safely <- function(x) {
  tryCatch(
    {
      if (!is.numeric(x)) stop("need a number")
      x * 2
    },
    error   = function(e) { message("error: ", conditionMessage(e)); -1 },
    finally = message("done attempting")   # always runs
  )
}

read_safely(5)    # done attempting  /  -> [1] 10
read_safely("a")  # error: need a number  /  done attempting  /  -> [1] -1

4️⃣ try() and stopifnot()

try() is the lightweight option: it prints an error but lets your script keep going, returning a "try-error" object you can test. stopifnot() is a one-line input guard — it errors unless every condition is TRUE, naming the one that failed.

# try() is the lightweight version: it prints the error but does NOT
# stop the surrounding script, returning a try-error object instead.
result <- try(log("oops"), silent = TRUE)
class(result)            # -> [1] "try-error"

# stopifnot() is a concise guard: it errors UNLESS every test is TRUE.
checked_mean <- function(x) {
  stopifnot(is.numeric(x), length(x) > 0)   # validate inputs up front
  mean(x)
}
checked_mean(c(2, 4, 6))  # passes -> [1] 4
# checked_mean("hi")  would error: is.numeric(x) is not TRUE

Your turn. Fill in the ___ blank, run it, and compare with the expected output.

Mini-Challenge: A Robust Parser

This ties everything together: validate with stopifnot(), raise your own stop(), catch it with tryCatch(), and clean up with finally — the shape of every robust function.

# 🎯 MINI-CHALLENGE: a robust parser.
#
# Write parse_num(x) that:
# 1. Uses stopifnot(length(x) == 1) to require a single value.
# 2. Inside tryCatch, does as.numeric(x); if the result is NA,
#    call stop("not a number").
# 3. Catches the error and returns 0 instead.
# 4. Uses finally = to message("parse attempt finished").
#
# Then test: parse_num("42")  and  parse_num("abc")
#
# ✅ Example output:
#    parse attempt finished
#    [1] 42
#    parse attempt finished
#    [1] 0

# your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Error Handling

TaskR Syntax
Raise a fatal errorstop("msg")
Raise a warningwarning("msg")
Catch an errortryCatch(expr, error = function(e) ...)
Always run cleanuptryCatch(expr, finally = ...)
Read the messageconditionMessage(e)
Validate inputsstopifnot(is.numeric(x))

🎉 Lesson Complete!

Practice quiz

Which function raises a fatal ERROR that halts execution?

  • message()
  • stop()
  • warning()
  • cat()

Answer: stop(). stop() signals an error and aborts the call unless it is caught.

Which function signals a problem but lets execution CONTINUE?

  • warning()
  • stop()
  • abort()
  • halt()

Answer: warning(). warning() flags something suspicious but does not stop the code.

Which function prints an informational note to the console (stderr)?

  • stop()
  • warning()
  • print()
  • message()

Answer: message(). message() emits an informational note without implying an error.

What does tryCatch() do when a matching condition fires?

  • Ignores it silently
  • Runs the matching handler and returns its value
  • Restarts R
  • Prints the call stack and quits

Answer: Runs the matching handler and returns its value. tryCatch() stops evaluating the expression and runs the matching handler, returning what it returns.

Which tryCatch() argument always runs, whether or not an error occurred?

  • always =
  • ensure =
  • finally =
  • cleanup =

Answer: finally =. finally = runs at the end no matter what, ideal for cleanup.

Which function extracts the human-readable text from a condition object e?

  • conditionMessage(e)
  • message(e)
  • errorText(e)
  • e$text

Answer: conditionMessage(e). conditionMessage(e) returns the condition's message string.

What does try() return when its expression errors?

  • NULL
  • A warning object
  • An object of class "try-error"
  • The number 0

Answer: An object of class "try-error". try() prints the error but returns a "try-error" object so the script continues.

What does stopifnot(is.numeric(x), length(x) > 0) do?

  • Always returns TRUE
  • Prints x
  • Loops over x
  • Errors unless every condition is TRUE

Answer: Errors unless every condition is TRUE. stopifnot() throws an informative error the moment any condition is not entirely TRUE.

In tryCatch(), how is an error handler specified?

  • error = function(e) { ... }
  • onError: { ... }
  • catch(e) { ... }
  • except e: ...

Answer: error = function(e) { ... }. Handlers are named arguments like error = function(e) ... and warning = function(w) ...

In tryCatch(), what becomes the overall result when a handler runs?

  • Always NA
  • Always NULL
  • The handler's return value
  • The original error object

Answer: The handler's return value. The matching handler's return value becomes the result of the tryCatch() call.

Continue this course

Frequently asked questions

What is the difference between stop(), warning(), and message()?

These signal the three condition types. stop() raises an error: it immediately halts execution and, unless caught, prints 'Error: ...' and aborts the call. warning() raises a warning: execution continues and the warning is shown (often collected and printed at the end). message() emits an informational note to the console (technically to stderr) without implying anything is wrong — use it for progress updates. Errors are for 'I cannot continue', warnings for 'this is suspicious but I carried on', and messages for 'here is what I'm doing'.

How does tryCatch() work?

tryCatch() evaluates its first argument (the expression) and watches for conditions. You supply named handler functions — error =, warning =, message = — and if a matching condition is signalled, R stops evaluating the expression and runs that handler instead, returning whatever the handler returns. This means a caught error no longer crashes your program; you decide the fallback. The optional finally = block runs at the very end whether or not a condition occurred, which is ideal for cleanup like closing files or connections.

When should I use try() instead of tryCatch()?

try() is a simpler, older tool: it runs an expression and, if it errors, prints the message but lets the surrounding code continue, returning an object of class 'try-error' you can test for. Use it for quick, one-off protection when you do not need different behaviour for warnings versus errors. tryCatch() is more powerful and explicit — it lets you handle each condition type separately, return custom fallback values, and run finally cleanup — so prefer it for anything beyond the most casual case.

What is stopifnot() for?

stopifnot() is a compact way to validate assumptions at the top of a function. You pass it one or more logical expressions, and it throws an informative error the moment any of them is not entirely TRUE, naming the failing test. So stopifnot(is.numeric(x), length(x) > 0) guarantees x is a non-empty numeric vector before the rest of the function runs. It encourages 'fail fast' programming: catch bad inputs immediately with a clear message rather than letting them cause a confusing error deep inside your code.