Raising Exceptions in Ruby
Fast recall
Quick revision
Core answer
raise stops normal execution and sends an exception up the call stack. raise "message" creates a RuntimeError; raise SomeError, "message" creates the named error; and raise error_object sends an existing instance. Inside a rescue clause, bare raise re-raises the current exception with its original backtrace, which is the right choice when the handler cannot fully recover.Whether you know the forms of raise (message -> RuntimeError, class+message, instance) and when to raise (violated precondition, not flow).
Raising for normal control flow (use a return value), or raise with no class (RuntimeError) where a specific class fits.
raise 'msg' (RuntimeError), raise Class, 'msg', raise Instance. Raise on a violated precondition, not for normal flow; pick a specific class.
Interview-ready guide
Interview answer
raise reports that the current operation cannot complete normally. Ruby stops the remaining statements on that path and unwinds method calls until it finds a matching rescue; ensure clauses still run during the unwind. An exception is therefore not just a message or special return value—the next normal line does not execute.
raise 'bad value' creates a RuntimeError. raise ArgumentError, 'quantity must be positive' creates a named category, and raise error_object sends an existing instance with its structured fields. The class should communicate what failed, while the message explains this occurrence. Callers can rescue the class without relying on fragile message matching.
Raise before a state change when an argument breaks the method contract. In the example, zero quantity raises ArgumentError before anything enters the order. The inner rescue records the same object and uses bare raise; the outer boundary confirms that the re-raised exception is identical and chooses the visible response.
Inside a rescue, bare raise is the safest way to propagate the current exception after adding context or metrics because it preserves the object and original backtrace. Wrapping in a higher-level error can be useful at a real abstraction boundary, but preserve the original cause. Expected alternatives such as an optional search miss may be clearer as nil or a result object when the API does not define absence as exceptional.
Raising Exceptions in Ruby
Direct Answer
Ruby's raise stops normal execution and sends an exception up the call stack. raise "message" creates a RuntimeError; raise SomeError, "message" creates the named error; and raise error_object sends an existing instance. Inside a rescue clause, bare raise re-raises the current exception with its original backtrace, which is the right choice when the handler cannot fully recover.
Deep Dive & Architecture
The important effect of raise is control transfer. Ruby does not return a special error value and it does not run the next line. It starts unwinding frames until a compatible rescue is found, while ensure clauses encountered along the way still run.
The chosen class communicates category; the message supplies instance detail. ArgumentError says a caller broke an argument contract. A domain class can say a business rule failed. Using RuntimeError for every condition forces handlers to inspect prose and makes unrelated failures look identical.
Re-raising is different from wrapping. Bare raise in a handler preserves the same object and backtrace. Raising a new higher-level exception may be appropriate at an abstraction boundary, but the original error should remain available through logging or an explicit cause so diagnosis does not lose its starting point.
An exception is costly mainly in clarity when used as routine branching. Methods should document whether absence, validation failure, and external failure arrive as values or exceptions. Consistent contracts let callers handle each outcome without broad rescue blocks.
Code Example
class Order
attr_reader :items
def initialize
@items = []
end
def add(product, quantity)
@items << [product, quantity]
end
end
def add_item(order, product, quantity)
raise ArgumentError, 'quantity must be positive' unless quantity.positive?
order.add(product, quantity)
rescue ArgumentError
raise # same object and backtrace
end
# A caller can rescue the known contract failure:
order = Order.new
begin
add_item(order, 'book', 0)
rescue ArgumentError => error
puts error.message # => quantity must be positive
end
Common Mistakes & Pitfalls
Raising for normal control flow (use a return value), or raise with no class (RuntimeError) where a specific class fits.
Check your understanding
Answer these without looking back at the article.
- What is the difference between `raise`, `raise ErrorClass`, and `raise error`?
- What does bare `raise` do inside a rescue block?
- When is returning `nil` clearer than raising an exception?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.