skip to content

Rescue, Ensure & Retry

begin/rescue/else/ensure handles an error, runs code only on success and always cleans up; retry reruns the block. Interviewers probe clause order, return values and a return inside ensure.

on this pageshow

explore

questions

6

In Ruby, in what order do the begin, rescue, else and ensure clauses run, and what value does the whole expression return?

level: juniorimportance: must knowfreq 78%

answer

  1. body first, then one branch
  2. else only when nothing raised
  3. ensure always runs, last
  4. ensure's value is discarded
  5. else errors skip sibling rescues

basics

~20 s

The body runs; on an exception the first matching rescue runs, otherwise else runs; ensure always runs last. The expression's value comes from the rescue, from else, or from the body when there is no else; ensure's value is discarded.

solid answer

~50 s

Ruby runs the `begin` body first. If it raises, Ruby tries the `rescue` clauses top to bottom and runs the first that matches; if nothing raised, the `else` clause runs instead. `ensure` runs last on every path: after a rescue, after `else`, when the exception matched no clause and keeps propagating, and when control leaves early through `return`, `break` or `next`. The value of the whole expression is the last value of whichever branch finished normally: the rescue body, the `else` body, or the `begin` body if there is no `else`. The `ensure` body's value is thrown away unless it uses an explicit `return`. An exception raised inside `else` is not handled by that same begin's rescue clauses, so `else` is the place for code you want protected by `ensure` but not by the rescues.

code

ruby · 17 lines
ruby
input = "42"

width = begin
  puts "body"
  Integer(input)
rescue ArgumentError
  puts "rescue"
  0
else
  puts "else"
  :parsed
ensure
  puts "ensure"
  :ignored
end

p width   # => :parsed  (with input = "x": prints body, rescue, ensure; width is 0)

go deeper

for a junior

Recall the order: body, then one of rescue or else, then ensure, and that ensure always runs.

for a middle

Explain the three paths and the value rules, especially that ensure's value is dropped and that else is outside the rescue's protection.

for a senior

Use else deliberately to keep the protected region small, and put all resource cleanup in ensure so early returns and unrescued errors still release it.

for a principal

Review handler shape as a team convention: narrow rescue regions, success work in else, cleanup only in ensure, so failures are attributed to the right line.

## The four clauses A Ruby **exception handler** is a `begin ... end` expression with up to four parts: | Clause | Runs when | Value used? | |---|---|---| | `begin` body | always, first | yes, when there is no `else` and nothing raised | | `rescue` (one or more) | the body raised an exception the clause matches | yes, when it runs | | `else` | the body finished without raising | yes, when it runs | | `ensure` | always, last | no, unless it uses an explicit `return` | An `else` needs at least one `rescue`; Ruby rejects a lonely `else` in a `begin` block as a syntax error, since it would be pointless. ## The three paths Every run follows one of three paths: 1. **Nothing raised.** Body, then `else` (if present), then `ensure`. The value is the `else` value, or the body's value when there is no `else`. 2. **Raised and rescued.** Body up to the failing line, then the **first** matching `rescue` clause, then `ensure`. The value is the rescue body's last value. The rest of the body is skipped, and `else` does not run. 3. **Raised and not rescued.** Body up to the failing line, then `ensure`, then the exception keeps propagating to the caller. There is no value. `ensure` also runs when the body leaves early: a `return` from a method, a `break` or `next` inside a block, or a `throw`. That is what makes it the right home for closing files, sockets and locks. ## Return values in detail - The expression's value is the **last evaluated expression of the branch that completed**: rescue, `else`, or body. - The **`ensure` value is discarded**. The syntax documentation puts it plainly: without an explicit `return` in `ensure`, the block returns the last statement evaluated before entering `ensure`. - Because `begin ... end` is an expression, it can sit on the right of an assignment: `count = begin ... end`. - A method whose whole body is the handler returns the same value, since a method returns its last expression. ## Why else exists Code in `else` runs only on success, but outside the protection of the rescue clauses. That matters when the success path can itself fail in ways you do not want to mask: - If the body parses a file and the `else` clause writes the result somewhere, a failure while writing propagates instead of being reported as "could not parse". - Keeping the protected region small makes the rescue honest: it only catches errors from the lines it was written for. - `ensure` still runs after `else`, so cleanup is not affected. ## A worked example In an image-thumbnail service, parsing a width parameter might look like the snippet below. With `"42"` the output is `body`, `else`, `ensure`, and the value is `:parsed`; with `"x"` it is `body`, `rescue`, `ensure`, and the value is `0`. The `:ignored` at the end of `ensure` never becomes the result. ## Common misreadings - **"`ensure` returns its value."** Only an explicit `return` changes the result, and that has serious side effects of its own. - **"`else` runs after a rescue."** It runs only when nothing was raised. - **"All matching rescue clauses run."** Only the first match runs. - **"`ensure` is skipped when no rescue matches."** It runs before the exception leaves the block. ## How interviewers probe it Expect the question in the form of a snippet with `puts` calls in every clause, asked twice: once with input that succeeds and once with input that fails. The reliable way to answer is to trace the three paths above rather than guess: 1. Mark the line that raises, if any; everything after it in the body is skipped. 2. Find the first rescue clause whose class matches; if none does, jump straight to `ensure` and then out. 3. If nothing raised, run `else`. 4. Run `ensure` and remember that its last line does not count. 5. Name the value: the last line of whichever branch from steps 2 or 3 completed. A frequent variation adds a `return` inside the body. The answer is the same shape: `ensure` still runs, and the returned value is kept.

  • If the else clause raises, does the rescue clause above it handle the error?
    No. The rescue clauses of a `begin` cover only its body. An exception raised in `else` propagates to the caller after `ensure` runs. That is the point of `else`: success-only code that should not be mistaken for a failure of the protected lines.
  • Does ensure run when the method returns early from inside the begin body?
    Yes. `ensure` runs whenever control leaves the block, including `return` from a method and `break` or `next` from a block. The early return value is kept, because the `ensure` value is discarded unless `ensure` itself uses an explicit `return`.

saying these in an interview costs you the question

  • The value of ensure becomes the value of the begin block
  • else runs after a rescue clause has handled the error
  • Every rescue clause that matches the exception runs in turn
  • ensure is skipped when no rescue clause matches
  • An exception raised in else is caught by the same begin's rescue
open as a page

In Ruby, how does retry behave inside a rescue clause, and how do you retry a payment-gateway call a bounded number of times?

level: middleimportance: must knowfreq 58%

basics

~20 s

retry, legal only inside a rescue clause, restarts the begin body (or method or do...end body) from its first line. Keep an attempt counter outside that body, retry while under a limit, then re-raise; ensure runs once, after the last attempt.

open as a page

In Ruby, how do you attach rescue and ensure to a method body or a do...end block without begin, and when is begin still needed?

level: juniorimportance: should knowfreq 42%

basics

~20 s

A def body, a class or module body and a do...end block act as an implicit begin, so rescue, else and ensure can follow directly. Use begin to cover only part of the body, or inside a brace block, which cannot take rescue.

open as a page

In Ruby, what does the rescue modifier in value = expr rescue fallback catch, and why do style guides discourage it?

level: juniorimportance: should knowfreq 48%

basics

~20 s

It evaluates expr and, if any StandardError is raised, uses fallback instead; since the modifier binds tighter than =, value gets the fallback. It cannot name a class, so it also hides bugs such as NoMethodError from a typo.

open as a page

In Ruby, how does a begin block choose among several rescue clauses, and why must specific classes come before broader ones?

level: middleimportance: should knowfreq 45%

basics

~20 s

Ruby tests rescue clauses top to bottom and runs only the first whose listed class or module matches the exception via ===. A broad class listed first, like StandardError above ArgumentError, catches everything, so the narrower clause never runs.

open as a page

In Ruby, what happens when an ensure clause runs an explicit return while an exception is propagating out of the method?

level: seniorimportance: should knowfreq 34%

basics

~20 s

The explicit return wins: the method returns the ensure's value and the propagating exception is silently discarded, as if rescued. It also overrides a normal return value. RuboCop's Lint/EnsureReturn flags it; keep ensure for cleanup only.

open as a page