CrackedRuby logo

CrackedRuby

break, next, redo, retry

Overview

Ruby provides four essential keywords for controlling program flow within loops, iterators, and exception handling: break, next, redo, and retry. These keywords allow developers to alter the normal execution sequence of loops and blocks, providing fine-grained control over iteration behavior.

The break keyword terminates the innermost enclosing loop or iterator, returning control to the statement following the loop. The next keyword skips the remaining statements in the current iteration and moves to the next iteration. The redo keyword restarts the current iteration from the beginning without evaluating the loop condition again. The retry keyword restarts a block or method from the beginning and is primarily used in exception handling contexts.

# Basic break usage
[1, 2, 3, 4, 5].each do |n|
  break if n > 3
  puts n
end
# Output: 1, 2, 3

# Basic next usage  
[1, 2, 3, 4, 5].each do |n|
  next if n.even?
  puts n
end
# Output: 1, 3, 5

# Basic redo usage with condition
i = 0
[1, 2, 3].each do |n|
  i += 1
  redo if i < 2 && n == 1
  puts "#{n}: #{i}"
end

These keywords work within blocks passed to iterators like each, map, select, and traditional loop constructs like while, for, and loop. Understanding their behavior is crucial for writing effective Ruby code that handles complex iteration scenarios.

Basic Usage

Break Statement

The break keyword immediately exits the current loop or iterator, optionally returning a value. When used in a loop, execution continues with the first statement after the loop. In iterator blocks, break causes the iterator method to return the specified value or nil.

# Breaking from a while loop
count = 0
while true
  count += 1
  break if count >= 5
  puts count
end
puts "Loop ended at #{count}"

# Breaking from an iterator with return value
result = [1, 2, 3, 4, 5].each do |n|
  break "found it!" if n == 3
  puts n
end
puts result # "found it!"

# Breaking from nested loops - only breaks innermost
(1..3).each do |i|
  (1..3).each do |j|
    break if j == 2
    puts "#{i}, #{j}"
  end
  puts "Outer loop #{i} continues"
end

When break appears in a block without an explicit value, the iterator method returns nil. The break value becomes the return value of the entire iterator expression.

Next Statement

The next keyword skips the remaining code in the current iteration and proceeds to the next iteration of the loop. Unlike break, which exits the entire loop, next only affects the current iteration.

# Skipping even numbers
(1..10).each do |n|
  next if n.even?
  puts "Processing #{n}"
end

# Next with conditional logic
users = ['alice', '', 'bob', nil, 'charlie']
users.each do |user|
  next if user.nil? || user.empty?
  puts "Hello, #{user.capitalize}"
end

# Next in map operations
numbers = [1, 2, 3, 4, 5]
doubled = numbers.map do |n|
  next if n.even?  # returns nil for even numbers
  n * 2
end
# => [2, nil, 6, nil, 10]

When next appears in an iterator block, it causes the current iteration to end immediately and return nil to the collection being built (in methods like map).

Redo Statement

The redo keyword restarts the current iteration from the beginning of the block or loop body. The loop condition is not re-evaluated, and loop variables maintain their current values. This creates the potential for infinite loops if not used carefully.

# Redo with user input validation
attempts = 0
loop do
  attempts += 1
  puts "Attempt #{attempts}: Enter a number (1-10):"
  input = gets.chomp.to_i
  
  if input < 1 || input > 10
    puts "Invalid input!"
    redo if attempts < 3
    puts "Too many attempts"
    break
  end
  
  puts "You entered: #{input}"
  break
end

# Redo in iterator with condition
values = [1, 'invalid', 3, 4]
results = []
values.each_with_index do |val, idx|
  if val.is_a?(String)
    puts "Converting string at index #{idx}"
    val = idx + 1  # Convert to number
    redo  # Process the corrected value
  end
  results << val * 2
end

The redo keyword is particularly useful for retry logic within iterations, error recovery, and data validation scenarios where the current iteration needs to be repeated with modified conditions.

Retry Statement

The retry keyword restarts the execution of a block or method from the very beginning. It's most commonly used in exception handling to retry operations after handling recoverable errors.

# Basic retry in exception handling
attempts = 0
begin
  attempts += 1
  puts "Attempt #{attempts}"
  
  # Simulate unreliable operation
  raise "Network error" if rand < 0.7
  puts "Operation succeeded!"
  
rescue => e
  puts "Error: #{e.message}"
  retry if attempts < 3
  puts "Max attempts reached"
end

# Retry with exponential backoff
def fetch_data(url)
  attempts = 0
  begin
    attempts += 1
    # Simulate network request
    raise "Connection timeout" if rand < 0.5
    "Data from #{url}"
  rescue => e
    if attempts <= 3
      sleep_time = 2 ** attempts
      puts "Retry in #{sleep_time} seconds..."
      sleep(sleep_time)
      retry
    else
      raise "Failed after #{attempts} attempts: #{e.message}"
    end
  end
end

Advanced Usage

Breaking with Values in Complex Scenarios

Ruby allows break to return complex data structures and supports sophisticated flow control patterns in nested iterators and blocks.

# Breaking with complex return values
result = (1..100).each_with_object({}) do |num, hash|
  if num > 50
    break { status: :incomplete, processed: hash.keys.length, reason: "limit exceeded" }
  end
  
  next if num % 7 != 0  # Skip non-multiples of 7
  hash[num] = num ** 2
end

# Multi-level break simulation using exceptions
class BreakAll < StandardError; end

begin
  (1..5).each do |i|
    (1..5).each do |j|
      (1..5).each do |k|
        puts "#{i}, #{j}, #{k}"
        raise BreakAll if i * j * k > 20
      end
    end
  end
rescue BreakAll
  puts "Broke out of all loops"
end

# Using break in method definitions with blocks
def process_until_condition(items, &block)
  items.each_with_index do |item, index|
    result = block.call(item, index)
    break result if result.is_a?(Hash) && result[:stop]
  end
end

data = ['a', 'b', 'c', 'd', 'e']
final_result = process_until_condition(data) do |item, idx|
  puts "Processing #{item} at index #{idx}"
  if item == 'c'
    { stop: true, message: "Found target", index: idx }
  else
    item.upcase
  end
end

Advanced Redo Patterns

Complex redo scenarios often involve state management and condition tracking to prevent infinite loops while enabling sophisticated retry logic.

# Redo with state tracking for data processing
def process_with_correction(data)
  results = []
  corrections = {}
  
  data.each_with_index do |item, index|
    correction_count = corrections[index] ||= 0
    
    case item
    when String
      if item.empty? && correction_count == 0
        corrections[index] += 1
        item = "default_#{index}"
        redo
      elsif item =~ /\d+/ && correction_count == 1
        corrections[index] += 1  
        item = item.gsub(/\d+/, '')
        redo
      end
    when Numeric
      if item < 0 && correction_count == 0
        corrections[index] += 1
        item = item.abs
        redo
      end
    end
    
    results << { original_index: index, value: item, corrections: correction_count }
  end
  
  results
end

# Redo with dynamic condition evaluation
class DataProcessor
  def initialize(rules)
    @rules = rules
  end
  
  def process(items)
    items.map.with_index do |item, idx|
      retry_count = 0
      
      begin
        @rules.each do |rule|
          if rule[:condition].call(item)
            item = rule[:transform].call(item)
            retry_count += 1
            redo if retry_count < rule[:max_retries]
          end
        end
        item
      end
    end
  end
end

rules = [
  { 
    condition: ->(x) { x.is_a?(String) && x.length < 3 },
    transform: ->(x) { x + "_extended" },
    max_retries: 2
  }
]

processor = DataProcessor.new(rules)

Sophisticated Retry Mechanisms

Advanced retry patterns incorporate circuit breaker patterns, jitter, and sophisticated error classification to handle complex failure scenarios.

# Circuit breaker with retry
class CircuitBreaker
  def initialize(failure_threshold: 5, reset_timeout: 60)
    @failure_threshold = failure_threshold
    @reset_timeout = reset_timeout
    @failure_count = 0
    @last_failure_time = nil
    @state = :closed  # :closed, :open, :half_open
  end
  
  def call(&block)
    if @state == :open && Time.now - @last_failure_time < @reset_timeout
      raise "Circuit breaker is OPEN"
    end
    
    @state = :half_open if @state == :open
    
    attempts = 0
    begin
      attempts += 1
      result = block.call
      reset_circuit
      result
      
    rescue => e
      @failure_count += 1
      @last_failure_time = Time.now
      
      if @failure_count >= @failure_threshold
        @state = :open
        raise "Circuit breaker opened after #{@failure_count} failures"
      end
      
      if attempts <= 3
        sleep_with_jitter(attempts)
        retry
      else
        raise e
      end
    end
  end
  
  private
  
  def reset_circuit
    @failure_count = 0
    @state = :closed
    @last_failure_time = nil
  end
  
  def sleep_with_jitter(attempt)
    base_delay = 2 ** attempt
    jitter = rand(0.5..1.5)
    sleep(base_delay * jitter)
  end
end

# Usage with complex retry logic
breaker = CircuitBreaker.new(failure_threshold: 3, reset_timeout: 30)

def unreliable_api_call
  raise "API Error" if rand < 0.6
  "Success!"
end

result = breaker.call { unreliable_api_call }

Error Handling & Debugging

Common Break and Next Pitfalls

Understanding the scope and behavior of break and next in different contexts prevents common programming errors and unexpected behavior.

# Pitfall: Break in nested blocks
def problematic_break
  [1, 2, 3].each do |outer|
    [4, 5, 6].each do |inner|
      # This break only exits the inner each, not the method
      break if inner == 5
      puts "#{outer}, #{inner}"
    end
    puts "Outer continues: #{outer}"
  end
end

# Solution: Use exceptions for multi-level breaks
class MultiBreak < StandardError; end

def correct_multi_break
  begin
    [1, 2, 3].each do |outer|
      [4, 5, 6].each do |inner|
        raise MultiBreak if inner == 5
        puts "#{outer}, #{inner}"
      end
      puts "Outer continues: #{outer}"
    end
  rescue MultiBreak
    puts "Multi-level break executed"
  end
end

# Debugging break behavior in different contexts
def debug_break_behavior
  puts "=== In method with block ==="
  result = [1, 2, 3, 4, 5].map do |n|
    puts "Processing #{n}"
    if n == 3
      break "early exit"  # Causes map to return "early exit"
    end
    n * 2
  end
  puts "Result: #{result.inspect}"
  
  puts "\n=== In proc vs lambda ==="
  my_proc = Proc.new do |n|
    break "proc break" if n == 3  # This would cause LocalJumpError
    n * 2
  end
  
  my_lambda = lambda do |n|
    return "lambda return" if n == 3  # This works fine
    n * 2
  end
  
  # Safe usage patterns
  begin
    result = [1, 2, 3, 4].map(&my_proc)
  rescue LocalJumpError => e
    puts "LocalJumpError: #{e.message}"
  end
end

Redo and Retry Error Patterns

Improper use of redo and retry can lead to infinite loops and stack overflow errors. Implementing proper safeguards is essential.

# Dangerous redo pattern (infinite loop)
def dangerous_redo
  counter = 0
  [1, 2, 3].each do |n|
    counter += 1
    puts "Processing #{n}, attempt #{counter}"
    
    # BUG: This could loop forever
    if n == 2 && counter < 100  # Weak condition
      redo
    end
  end
end

# Safe redo pattern with proper guards
def safe_redo_with_guards
  [1, 2, 3].each_with_index do |n, idx|
    attempt_count = 0
    max_attempts = 3
    
    begin
      attempt_count += 1
      puts "Processing #{n} (attempt #{attempt_count})"
      
      # Simulate processing that might need redo
      if n == 2 && attempt_count == 1
        raise "Simulated error"
      end
      
      puts "Successfully processed #{n}"
      
    rescue => e
      if attempt_count < max_attempts
        puts "Error: #{e.message}, retrying..."
        redo
      else
        puts "Max attempts reached for #{n}, skipping"
        next
      end
    end
  end
end

# Retry with proper error classification
def classified_retry_pattern
  retryable_errors = [Errno::ECONNREFUSED, Timeout::Error, Net::ReadTimeout]
  permanent_errors = [ArgumentError, NoMethodError]
  
  attempts = 0
  max_attempts = 5
  
  begin
    attempts += 1
    puts "Attempt #{attempts}"
    
    # Simulate various error types
    error_type = [:retryable, :permanent, :success].sample
    
    case error_type
    when :retryable
      raise Errno::ECONNREFUSED, "Connection refused"
    when :permanent  
      raise ArgumentError, "Invalid argument"
    when :success
      return "Operation completed successfully"
    end
    
  rescue *retryable_errors => e
    if attempts < max_attempts
      backoff = [2 ** attempts, 30].min  # Cap at 30 seconds
      puts "Retryable error: #{e.message}. Retrying in #{backoff}s..."
      sleep(backoff)
      retry
    else
      raise "Max retry attempts (#{max_attempts}) exceeded: #{e.message}"
    end
    
  rescue *permanent_errors => e
    raise "Permanent error, not retrying: #{e.message}"
    
  rescue => e
    puts "Unexpected error type: #{e.class} - #{e.message}"
    raise
  end
end

Performance & Memory

Performance Implications of Flow Control

The choice of flow control keywords can significantly impact performance, especially in tight loops and high-volume data processing scenarios.

require 'benchmark'

# Performance comparison: break vs conditions
def benchmark_flow_control
  large_array = (1..1_000_000).to_a
  
  Benchmark.bm(15) do |x|
    x.report("with break:") do
      large_array.each do |n|
        break if n > 500_000
        # Minimal processing
        n * 2
      end
    end
    
    x.report("with condition:") do
      large_array.each do |n|
        next if n > 500_000
        # Minimal processing  
        n * 2
      end
    end
    
    x.report("select + break:") do
      large_array.select do |n|
        break if n > 500_000
        n.even?
      end
    end
    
    x.report("take_while:") do
      large_array.take_while { |n| n <= 500_000 }.select(&:even?)
    end
  end
end

# Memory usage patterns with different approaches
def memory_usage_comparison
  # Memory-efficient early termination
  def find_first_match_break(data)
    data.each do |item|
      if complex_condition?(item)
        return process_item(item)
      end
    end
    nil
  end
  
  # Memory-inefficient - processes all items
  def find_first_match_filter(data)
    data.select { |item| complex_condition?(item) }
        .first
        &.then { |item| process_item(item) }
  end
  
  # Memory-efficient with lazy evaluation
  def find_first_match_lazy(data)
    data.lazy
        .select { |item| complex_condition?(item) }
        .first
        &.then { |item| process_item(item) }
  end
  
  private
  
  def complex_condition?(item)
    item.to_s.length > 5 && item.to_s.include?('special')
  end
  
  def process_item(item)
    { processed: item, timestamp: Time.now }
  end
end

# Optimizing retry patterns for performance
class PerformantRetryManager
  def initialize(base_delay: 0.1, max_delay: 10, multiplier: 2)
    @base_delay = base_delay
    @max_delay = max_delay
    @multiplier = multiplier
  end
  
  def with_retry(max_attempts: 3, &block)
    attempts = 0
    start_time = Time.now
    
    begin
      attempts += 1
      result = block.call
      
      # Log performance metrics
      if attempts > 1
        total_time = Time.now - start_time
        puts "Succeeded after #{attempts} attempts in #{total_time.round(3)}s"
      end
      
      result
      
    rescue => e
      if attempts < max_attempts
        delay = calculate_delay(attempts)
        sleep(delay)
        retry
      else
        total_time = Time.now - start_time
        raise "Operation failed after #{attempts} attempts in #{total_time.round(3)}s: #{e.message}"
      end
    end
  end
  
  private
  
  def calculate_delay(attempt)
    delay = @base_delay * (@multiplier ** (attempt - 1))
    [delay, @max_delay].min
  end
end

Memory Leak Prevention

Improper use of flow control in long-running processes can lead to memory leaks, especially when combined with closures and object retention.

# Memory leak example with redo
class LeakyProcessor
  def initialize
    @cache = {}
  end
  
  def process_with_leak(items)
    items.each_with_index do |item, idx|
      attempt = 0
      
      begin
        attempt += 1
        
        # BUG: Cache grows indefinitely on redo
        cache_key = "#{idx}_#{attempt}_#{Time.now.to_f}"
        @cache[cache_key] = expensive_computation(item)
        
        # Simulate failure requiring redo
        if item.to_s.include?('retry') && attempt < 5
          redo
        end
        
        yield @cache[cache_key]
        
      ensure
        # BUG: Cache never cleaned up
      end
    end
  end
  
  private
  
  def expensive_computation(item)
    # Simulate expensive operation
    Array.new(1000) { "processed_#{item}_#{rand}" }
  end
end

# Memory-safe alternative
class SafeProcessor  
  def initialize(cache_size_limit: 100)
    @cache = {}
    @cache_size_limit = cache_size_limit
  end
  
  def process_safely(items)
    items.each_with_index do |item, idx|
      attempt = 0
      cache_key = nil
      
      begin
        attempt += 1
        cache_key = "#{idx}_#{attempt}"
        
        # Limit cache size to prevent memory leaks
        clean_cache if @cache.size >= @cache_size_limit
        
        @cache[cache_key] = expensive_computation(item)
        
        if item.to_s.include?('retry') && attempt < 3
          redo
        end
        
        yield @cache[cache_key]
        
      ensure
        # Clean up cache entry after use
        @cache.delete(cache_key) if cache_key
      end
    end
  end
  
  private
  
  def clean_cache
    # Remove oldest half of cache entries
    keys_to_remove = @cache.keys.first(@cache.size / 2)
    keys_to_remove.each { |key| @cache.delete(key) }
  end
  
  def expensive_computation(item)
    Array.new(100) { "processed_#{item}_#{rand}" }  # Smaller allocation
  end
end

Common Pitfalls

Scope and Context Confusion

One of the most frequent mistakes involves misunderstanding the scope in which flow control keywords operate, leading to unexpected behavior or runtime errors.

# Pitfall: Break in wrong context
def demonstrate_break_scope_issues
  puts "=== Break in Proc vs Lambda ==="
  
  # This will raise LocalJumpError
  my_proc = proc do |n|
    break if n > 3  # ERROR: break from proc-closure
    n * 2
  end
  
  # This works correctly
  my_lambda = lambda do |n|
    return if n > 3  # OK: return from lambda
    n * 2
  end
  
  begin
    [1, 2, 3, 4, 5].map(&my_proc)
  rescue LocalJumpError => e
    puts "LocalJumpError with proc: #{e.message}"
  end
  
  result = [1, 2, 3, 4, 5].map(&my_lambda)
  puts "Lambda result: #{result.inspect}"
end

# Pitfall: Break in method expecting block return
def method_expecting_each_value
  [1, 2, 3, 4, 5].each do |n|
    puts "Processing #{n}"
    if n == 3
      break "stopping here"  # each returns this value, not the array
    end
    n * 2
  end
end

# Correct alternative
def method_with_proper_collection
  result = []
  [1, 2, 3, 4, 5].each do |n|
    puts "Processing #{n}"
    if n == 3
      result << "stopping here"
      break
    end
    result << n * 2
  end
  result
end

# Pitfall: Next in map creating unexpected nils
def demonstrate_next_in_map
  # This creates unwanted nils
  numbers = [1, 2, 3, 4, 5, 6]
  doubled = numbers.map do |n|
    next if n.even?  # Returns nil for even numbers
    n * 2
  end
  puts "With next: #{doubled.inspect}"  # [2, nil, 6, nil, 10, nil]
  
  # Better alternatives
  doubled_compact = numbers.map do |n|
    n.even? ? nil : n * 2
  end.compact
  puts "Compacted: #{doubled_compact.inspect}"  # [2, 6, 10]
  
  # Or use select first
  doubled_filtered = numbers.reject(&:even?).map { |n| n * 2 }
  puts "Pre-filtered: #{doubled_filtered.inspect}"  # [2, 6, 10]
end

Infinite Loop Traps

Redo and retry can easily create infinite loops when exit conditions are not properly managed or when state isn't correctly updated.

# Dangerous pattern: Redo without proper state management
def infinite_redo_trap
  counter = 0
  
  [1, 2, 3].each do |n|
    puts "Processing #{n}"
    
    # BUG: counter never resets, condition never changes
    if n == 2 && counter < 5
      counter += 1
      redo  # This will loop forever if counter >= 5 already
    end
    
    counter = 0  # Reset happens too late
  end
end

# Safe pattern: Proper state management
def safe_redo_with_state
  [1, 2, 3].each_with_index do |n, idx|
    iteration_counter = 0  # Reset for each element
    
    begin
      iteration_counter += 1
      puts "Processing #{n}, iteration #{iteration_counter}"
      
      # Simulate condition requiring redo
      if n == 2 && iteration_counter == 1
        raise "Temporary error"
      end
      
      puts "Successfully processed #{n}"
      
    rescue => e
      if iteration_counter < 3
        puts "Retrying #{n}: #{e.message}"
        redo
      else
        puts "Max attempts reached for #{n}, moving on"
        next
      end
    end
  end
end

# Pitfall: Retry without changing conditions
def infinite_retry_trap
  attempts = 0
  
  begin
    attempts += 1
    puts "Attempt #{attempts}"
    
    # BUG: Condition never changes, will retry forever
    raise "This will always fail" if true
    
  rescue => e
    retry if attempts < 5  # This check helps but doesn't fix the logic error
  end
end

# Solution: Ensure conditions can change
def safe_retry_pattern
  attempts = 0
  max_attempts = 5
  success_probability = 0.3  # Start with low probability
  
  begin
    attempts += 1
    puts "Attempt #{attempts}, success probability: #{success_probability}"
    
    # Improve chances with each attempt
    success_probability += 0.2
    
    # Simulate operation that might succeed
    if rand > success_probability
      raise "Random failure"
    else
      puts "Operation succeeded!"
      return "Success"
    end
    
  rescue => e
    if attempts < max_attempts
      puts "Failed: #{e.message}, retrying..."
      retry
    else
      raise "Failed after #{max_attempts} attempts"
    end
  end
end

Exception Handling Integration Issues

Combining flow control keywords with exception handling can lead to subtle bugs and unexpected behavior patterns.

# Pitfall: Break/next inside ensure blocks
def problematic_ensure_flow
  [1, 2, 3, 4, 5].each do |n|
    begin
      puts "Processing #{n}"
      raise "Error on #{n}" if n == 3
      
    rescue => e
      puts "Caught: #{e.message}"
      next  # Skip to next iteration
      
    ensure
      puts "Ensure block for #{n}"
      # PITFALL: break/next here affects the iterator, not the begin block
      break if n == 4  # This will exit the each loop, not just the begin block
    end
    
    puts "After begin block for #{n}"
  end
end

# Correct approach: Handle flow control outside ensure
def correct_ensure_flow
  [1, 2, 3, 4, 5].each do |n|
    should_break = false
    should_continue = false
    
    begin
      puts "Processing #{n}"
      raise "Error on #{n}" if n == 3
      
    rescue => e
      puts "Caught: #{e.message}"
      should_continue = true
      
    ensure
      puts "Ensure block for #{n}"
      should_break = true if n == 4
    end
    
    next if should_continue
    break if should_break
    
    puts "After begin block for #{n}"
  end
end

# Pitfall: Retry changing exception context
def retry_context_confusion
  outer_variable = "initial"
  
  begin
    puts "Outer variable: #{outer_variable}"
    
    begin
      inner_variable = "inner_#{Time.now.to_i}"
      puts "Inner variable: #{inner_variable}"
      
      # Modify outer state
      outer_variable = "modified_#{Time.now.to_i}"
      
      raise "Inner error"
      
    rescue => e
      puts "Inner rescue: #{e.message}"
      # PITFALL: retry restarts the inner begin, but outer_variable
      # keeps its modified value from previous attempts
      retry if outer_variable.count('_') < 3
      raise "Escalated: #{e.message}"
    end
    
  rescue => e
    puts "Outer rescue: #{e.message}"
    puts "Final outer variable: #{outer_variable}"
  end
end

Reference

Flow Control Keywords Summary

Keyword Context Behavior Return Value Scope
break Loops, Iterators Exits immediately Optional value (default: nil) Innermost enclosing loop/iterator
break val Loops, Iterators Exits with value val Innermost enclosing loop/iterator
next Loops, Iterators Skips to next iteration nil to collection (in map, etc.) Current iteration only
redo Loops, Iterators Restarts current iteration N/A Current iteration
retry begin/rescue blocks, methods Restarts from beginning N/A Enclosing begin block or method

Iterator Method Behavior

Method With break With next With redo Notes
each Returns break value Continues iteration Repeats current iteration Most common usage
map Returns break value Adds nil to result Repeats current iteration next creates nils
select Returns break value Skips element Repeats current iteration next excludes element
reject Returns break value Skips element Repeats current iteration Opposite of select
find Returns break value Continues search Repeats current iteration Stops on first match
reduce Returns break value Skips element Repeats current iteration Accumulator unchanged on next

Exception Context Compatibility

Context break next redo retry Notes
while/until loops Traditional loops
for loops Ruby-style for loops
loop method Infinite loop construct
Iterator blocks each, map, etc.
begin/rescue Exception handling only
Method body When method has rescue
Proc objects ✗* *Raises LocalJumpError
Lambda objects More permissive than procs

Common Error Types

Error Cause Solution
LocalJumpError break/next in wrong context Use proper loop/iterator context
LocalJumpError break in Proc Use lambda or return instead
Infinite loop redo/retry without exit condition Add attempt counters and limits
Stack overflow Excessive retry calls Limit retry attempts
Unexpected nil next in map operations Use select first or compact after
Logic errors Flow control in ensure blocks Handle flow control outside ensure

Performance Characteristics

Pattern Time Complexity Memory Usage Best For
Early break O(k) where k < n O(1) additional Finding first match
next filtering O(n) O(n) for collections Conditional processing
redo with counter O(k*m) where m = retries O(1) additional Error recovery
Exponential backoff retry Varies O(1) Network operations
Circuit breaker pattern O(1) when open O(1) Cascading failure prevention