rubyguides

Kernel#throw

throw(tag [, value])

The throw method is used in conjunction with catch for flow control. Unlike exceptions, throw is meant for normal flow control and must be caught by a matching catch block.

How it works

throw raises a symbol that Ruby looks for in the call stack. When found, execution jumps to the matching catch block. Unlike raise, it’s designed for controlled jumps.

Practical Examples

Basic Usage

# Define a catch block
result = catch(:done) do
  throw :done if condition
  # More code
  "completed"
end

The basic pattern wraps a code block with catch(:symbol) and then uses throw :symbol anywhere inside that block to jump back to the catch point. If throw is never called, the catch block returns the value of the last expression, which is "completed" in this case. The symbol is a named label that Ruby searches for up the call stack.

Exiting nested loops

# Instead of breaking multiple levels
catch :exit_loop do
  (1..10).each do |x|
    (1..10).each do |y|
      if x == 5 && y == 5
        throw :exit_loop, [x, y]  # Jump out of both loops
      end
    end
  end
end
# => [5, 5]

The nested-loop example is a classic use case for throw/catch. Breaking out of two nested loops normally requires a flag or a labeled break, but throw gives you a single, clean escape hatch. The return value [5, 5] is passed through the catch block, so you can retrieve the coordinates where the search stopped without inspecting any intermediate state.

Early return from deep call stack

def level1
  level2
end

def level2
  level3
end

def level3
  throw :early_exit, "found it!"
end

result = catch(:early_exit) do
  level1
  "not found"
end

puts result  # => "found it!"

This example demonstrates how throw can escape from an arbitrarily deep call stack. The level3 method throws :early_exit and execution jumps all the way back to the matching catch block, skipping level2 and level1 entirely. The string "not found" is never reached, which is exactly the desired behavior when the value was found early.

Finding Items

# Find first matching item in nested structure
def find_user(users)
  catch(:found) do
    users.each do |group|
      group.each do |user|
        throw(:found, user) if user[:admin]
      end
    end
    nil
  end
end

users = [[{name: "a"}, {name: "b", admin: true}], [{name: "c"}]]
find_user(users)  # => {:name=>"b", :admin=>true}

The find_user method uses throw to short-circuit the search the moment an admin user is found. This is more direct than using break with a label or setting a found flag, because the control flow reads left to right: start searching, and if you see an admin, exit immediately with that value.

State Machines

class StateMachine
  def initialize
    @state = :start
  end
  
  def run
    catch(:done) do
      loop do
        case @state
        when :start
          @state = :processing
        when :processing  
          @state = :finished
        when :finished
          throw :done, "Success"
        end
      end
    end
  end
end

The state machine example shows how throw can act as a terminal transition. When the machine reaches :finished, the throw :done call jumps out of both the case statement and the enclosing loop, returning the result directly to the catch block. Without throw, you would need a flag variable to signal the loop to stop.

throw vs raise

# throw - for flow control, must be caught
throw :tag, value  # Jumps to catch

# raise - for exceptions, can be rescued
raise "error"      # Raises exception, can be rescued

The comparison between throw and raise highlights their different roles in Ruby’s control flow toolkit. throw is for structured, expected jumps within your own code, while raise signals that something went wrong and needs to be handled by a rescue block. Choosing the right one keeps the intent clear: use throw/catch when the exit is normal, and raise/rescue when it is exceptional.

With Values

# Throw can pass a value to catch
result = catch(:calculate) do
  # Complex computation
  throw(:calculate, 42) if some_condition
  100
end

puts result  # => 42

throw/catch is useful for complex control flow where breaking out of multiple levels would be awkward with simple returns. The pattern shows up most often in search and enumeration code, where the goal is to stop as soon as a match is found rather than continuing to process the rest of the collection.

using throw for non-local exits

throw works best when the code needs to leave a deep structure without treating the situation as an error. That makes it useful for early exits in searches, state machines, and other routines that already know what they want to do next. The tag should be specific enough that the reader can follow the jump, and the matching catch should be close enough to keep the path visible. When the jump starts to spread across too many layers, the control flow can become hard to explain.

It is also a reminder that control flow should match the kind of event being described. A normal search result or a found value can use throw and catch when that jump is the simplest path back out. If the situation is actually an error, raise is usually the clearer choice. That keeps the code honest about whether it is returning early or reporting failure.

The method can feel elegant in small nested loops because it gives the caller one clear way to escape. That is often enough for search helpers or tiny state machines where the main job is to stop once the answer is found. The key is to keep the tag name meaningful so the jump still reads like part of the program instead of a hidden trick.

See Also