Question
In Ruby, how can you stop execution from inside a block and return from the outer method?
For example:
class Bar
def do_things
Foo.some_method(x) do |x|
y = x.do_something
return y_is_bad if y.bad?
y.do_something_else
end
keep_doing_more_things
end
end
And the helper method that yields to the block looks like this:
class Foo
def self.some_method(targets, &block)
targets.each do |target|
begin
r = yield(target)
rescue
failed << target
end
end
end
end
The goal is to make the outer do_things method stop immediately when a bad value is found, without putting application-specific logic inside Foo.some_method. What is the correct Ruby approach here, and how do return, break, and exceptions behave in blocks?
Short Answer
By the end of this page, you will understand how control flow works inside Ruby blocks, especially the difference between return, break, and next. You will also see why a return inside a block can exit the outer method, when that is appropriate, and how to design iterator methods so they remain generic and reusable.
Concept
Ruby blocks are not methods. They are chunks of code passed into another method and executed with yield or by calling a block object.
That difference matters because flow-control keywords behave differently inside blocks than inside normal methods.
Key ideas:
returninside a block tries to return from the method where the block was originally defined.breakexits the current iterator or yielding method.nextskips to the next iteration of the block.raisethrows an exception and should be used for actual exceptional conditions, not ordinary loop control unless that design is intentional.
In your example, the block is defined inside Bar#do_things. That means:
return y_is_bad if y.bad?
tries to return from do_things, not from Foo.some_method.
This is one of Ruby's important block behaviors: a block can perform a non-local return.
Why this matters in real programming:
- You often pass blocks to iterator methods like
each,map,find, or custom helper methods. - You need to know whether you are stopping just the iteration or the whole outer method.
- Misusing
returncan make code confusing or cause errors in some contexts. - Good reusable methods should not need app-specific logic just to support early exit.
In your case, if Foo.some_method simply yields to the block, then return from the block can exit do_things directly. That keeps Foo generic.
Mental Model
Think of a Ruby block like a helper standing inside the outer method's room.
returnmeans: leave the whole room now.breakmeans: stop this current task.nextmeans: skip this item and continue with the next one.
So if do_things is the room, and Foo.some_method is just handing items to the helper, then return from inside the block says: "I am done with do_things entirely."
Syntax and Examples
Core flow-control keywords in Ruby blocks
array.each do |item|
next if item < 0 # skip this item
break if item == 5 # stop the loop
puts item
end
nextskips the current iteration.breakstops the loop or iterator.returnexits the surrounding method if used inside a block defined in that method.
Returning from the outer method
class Bar
def do_things(values)
values.each do |value|
return :bad_value if value < 0
puts value
end
:all_good
end
end
bar = Bar.new
p bar.do_things([1, 2, -1, 4])
# => :bad_value
Explanation:
- The block is defined inside
do_things. - When
return :bad_valueruns, Ruby returns fromdo_thingsimmediately. - The final
:all_goodis never reached.
Your case
class Bar
def do_things(targets)
Foo.some_method(targets) do |x|
y = x.do_something
return :y_is_bad if y.bad?
y.do_something_else
end
keep_doing_more_things
end
end
If y.bad? is true, do_things ends right there.
Using break instead
result = [1, 2, 3, 4].each do |n|
break :found_it if n == 3
end
p result
# => :found_it
break exits the iterator, not the outer method.
Using next instead
[1, -1, 2].each do |n|
next if n < 0
puts n
end
This prints:
1
2
Step by Step Execution
Consider this example:
class Foo
def self.some_method(targets)
targets.each do |target|
yield(target)
end
end
end
class Bar
def do_things(targets)
Foo.some_method(targets) do |x|
y = x * 2
return :bad if y > 5
puts y
end
:finished
end
end
bar = Bar.new
p bar.do_things([1, 2, 3, 4])
Execution trace
bar.do_things([1, 2, 3, 4])starts.Foo.some_method(targets)is called.some_methodbegins iterating over the array.- First item:
x = 1y = 2y > 5is falseputs 2
- Second item:
x = 2y = 4y > 5is falseputs 4
- Third item:
x = 3y = 6y > 5is truereturn :badexecutes
- Ruby immediately exits
do_things. :finishedis never evaluated.- Final result is
:bad.
Output:
2
4
Returned value:
:bad
Real World Use Cases
This kind of control flow appears often in Ruby programs.
Validation pipelines
Stop processing as soon as invalid data is found:
records.each do |record|
return :invalid if record.email.nil?
end
Request handling in web apps
End a controller or service method early when a condition fails:
def process_user(user)
return :not_found unless user
return :inactive if user.inactive?
:ok
end
Import scripts
Abort a larger operation when one item makes the whole batch unusable.
Filtering and scanning
When checking many items, you may want to stop at the first match or failure rather than continue unnecessarily.
Service objects
A method may call a generic iterator helper and use return in the block to exit the service method early without changing the helper's generic design.
Real Codebase Usage
In real Ruby codebases, developers usually choose among a few patterns.
1. Early return from the outer method
Good when the block is part of a larger method and failure should end that method immediately.
def process_items(items)
items.each do |item|
return :failed if item.invalid?
end
:success
end
2. Use predicate methods like any?, find, or detect
Sometimes a built-in enumerable expresses the intent more clearly.
bad_item = items.find(&:invalid?)
return :failed if bad_item
This is often easier to read than manual iteration.
3. Guard clauses
Ruby developers often prefer short early exits:
def do_things(targets)
return :no_targets if targets.empty?
Foo.some_method(targets) do |target|
result = target.do_something
return :bad if result.bad?
end
:ok
end
4. Avoid rescuing too broadly
Your Foo.some_method currently rescues everything:
rescue
In real code, this is risky because it may swallow important exceptions, including control-flow-related ones in some designs. Prefer rescuing specific error classes.
rescue StandardError => e
failed << target
end
5. Keep helper methods generic
A generic iterator method should usually just yield and let the caller decide what early exit means. That is exactly why return from the calling method's block can be useful.
Common Mistakes
Mistake 1: Thinking break returns from the outer method
Broken expectation:
def example
[1, 2, 3].each do |n|
break :stop if n == 2
end
:done
end
p example
# => :done
Why:
breakstopseach- it does not return from
example
If you want to exit example, use return.
Mistake 2: Rescuing everything
Problematic code:
begin
yield(target)
rescue
failed << target
end
Why this is bad:
- It hides real bugs.
- It makes debugging harder.
- It may swallow exceptions you did not intend to handle.
Better:
begin
yield(target)
rescue StandardError => e
failed << target
end
Mistake 3: Using exceptions for normal control flow
Possible, but usually not ideal:
raise "bad" if y.bad?
Use this only if the condition is truly exceptional or if your design explicitly uses exceptions for aborting a process.
Mistake 4: Confusing blocks with lambdas
A return in a lambda behaves differently from a normal block.
my_lambda = -> { return :from_lambda }
This returns from the lambda itself, not the surrounding method.
Mistake 5: Forgetting that return may skip cleanup code
If you return early, code after the yielding call will not run.
def do_things(items)
Foo.some_method(items) do |item|
return :bad if item.bad?
end
puts "This may never run"
end
If cleanup must always happen, use ensure or restructure the method.
Comparisons
return vs break vs next in Ruby blocks
| Keyword | What it does | Stops iteration? | Exits outer method? | Typical use |
|---|---|---|---|---|
return | Returns from the method where the block was defined | Yes | Yes | End the whole method early |
break | Exits the current block/iterator | Yes | No | Stop looping |
next | Skips the current iteration | No | No | Ignore one item and continue |
Block vs lambda
| Feature | Block | Lambda |
|---|---|---|
| Passed implicitly to a method | Yes | No |
return behavior | Can return from outer method | Returns from lambda itself |
| Common use | Iteration, DSL-style APIs | Callable object, reusable function |
Custom iterator vs Enumerable helpers
| Approach | Best for |
|---|---|
| Custom yielding method | Encapsulating repeated iteration logic |
find / detect | Stop at first matching item |
any? | Check whether any item matches |
map | Transform every item |
each | General iteration with side effects |
If your goal is “stop when you find a bad item,” find or any? may be clearer than each in many cases.
Cheat Sheet
Quick rules
- A Ruby block is not a method.
returninside a block returns from the surrounding method where the block was defined.breakexits the iterator or yielding method.nextskips to the next iteration.- Prefer specific
rescueclauses, not barerescue.
Common patterns
# Exit outer method early
items.each do |item|
return :bad if item.bad?
end
# Stop loop only
items.each do |item|
break if item.bad?
end
# Skip one item
items.each do |item|
next if item.nil?
puts item
end
Safer rescue
begin
yield(target)
rescue StandardError => e
failed << target
end
Often clearer alternatives
return :bad if items.any?(&:bad?)
bad_item = items.find(&:bad?)
return :bad if bad_item
Important edge case
return from a block works when the block is used normally inside the defining method. In other contexts, especially when blocks are stored and called later or converted in certain ways, a non-local return can raise an error.
FAQ
How do I return from the outer method inside a Ruby block?
Use return inside the block. If the block was defined inside a method, Ruby returns from that outer method.
Does break return from the surrounding method in Ruby?
No. break only stops the current iterator or yielding method.
What is the difference between break and return in Ruby blocks?
break stops the loop. return exits the method where the block was defined.
Should I use raise to stop iteration in Ruby?
Usually no, unless the situation is truly exceptional. For normal control flow, prefer return, break, next, or a clearer enumerable method.
Why is a bare rescue dangerous in Ruby?
Because it catches too much and can hide real bugs. Rescue a specific exception class whenever possible.
Is a lambda the same as a block in Ruby?
No. They are related, but return behaves differently. A lambda returns from itself, while a block can return from the outer method.
Can I keep Foo.some_method generic?
Yes. A generic method can simply yield to the block. The calling method can decide whether to return early.
Is there a clearer alternative to manual iteration?
Often yes. Methods like find, detect, and any? can express intent more clearly than each with manual control flow.
Mini Project
Description
Build a small Ruby method that scans a list of orders and stops immediately if it finds an invalid order. This demonstrates how a block can trigger an early return from the outer method while a generic helper method stays reusable.
Goal
Create a method that returns :invalid_order as soon as a bad order is found, otherwise returns :processed.
Requirements
- Create a generic helper method that iterates over a collection and yields each item.
- Create an
Orderobject with aninvalid?check. - In the outer processing method, return early when an invalid order is found.
- Return
:processedif all orders are valid.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.