Ruby Yield

Passing blocks to methods for flexible code

🎯 What is Yield?

Yield allows methods to accept and execute blocks of code. It makes your methods flexible by letting callers customize behavior without changing the method itself, promoting reusable and elegant code.


# Simple yield example
def greet
  puts "Hello!"
  yield
  puts "Goodbye!"
end

greet { puts "Nice to meet you!" }
                                    

Output:

Hello!

Nice to meet you!

Goodbye!

Key Yield Concepts

📦

Basic Yield

Execute a block inside a method

yield
📤

Yield with Args

Pass values to the block

yield(value)

Block Given?

Check if block was provided

block_given?
🔁

Multiple Yields

Call block multiple times

3.times { yield }

🔹 Basic Yield Usage

The yield keyword transfers control to the block provided by the caller. When yield executes, Ruby runs the block's code, then returns control back to the method.

# Method with yield
def say_hello
  puts "Start"
  yield
  puts "End"
end

say_hello { puts "Hello from block!" }

Output:

Start

Hello from block!

End

🔹 Yield with Parameters

Pass data from your method to the block using yield with arguments. The block receives these values as parameters, allowing it to work with method data dynamically.

# Yield with arguments
def calculate(a, b)
  result = yield(a, b)
  puts "Result: #{result}"
end

calculate(5, 3) { |x, y| x + y }
calculate(5, 3) { |x, y| x * y }

Output:

Result: 8

Result: 15

🔹 Checking for Blocks

Use block_given? to make blocks optional in your methods. This prevents errors when no block is provided and allows your method to handle both cases gracefully.

# Optional block
def process_data(data)
  puts "Processing: #{data}"
  
  if block_given?
    yield(data)
  else
    puts "No custom processing"
  end
end

process_data("Hello")
process_data("Hello") { |d| puts "Custom: #{d.upcase}" }

Output:

Processing: Hello

No custom processing

Processing: Hello

Custom: HELLO

🔹 Multiple Yields

Call yield multiple times within a method to execute the block repeatedly. This is useful for iteration, callbacks, or any scenario where you need to run the same code multiple times.

# Yield multiple times
def repeat(times)
  times.times do |i|
    yield(i + 1)
  end
end

repeat(3) { |num| puts "Iteration #{num}" }

Output:

Iteration 1

Iteration 2

Iteration 3

🔹 Yield with Return Values

Blocks can return values back to the method through yield. The method can capture and use these return values to make decisions or perform calculations based on block results.

# Capture block return value
def transform(value)
  result = yield(value)
  puts "Original: #{value}, Transformed: #{result}"
end

transform(5) { |n| n * 2 }
transform("hello") { |s| s.upcase }

Output:

Original: 5, Transformed: 10

Original: hello, Transformed: HELLO

🔹 Practical Yield Example

Create a timing method that measures how long a block takes to execute. This demonstrates a real-world use case where yield enables flexible, reusable utility methods.

# Measure execution time
def measure_time
  start_time = Time.now
  yield
  end_time = Time.now
  puts "Execution time: #{(end_time - start_time).round(3)} seconds"
end

measure_time do
  sum = 0
  1000.times { |i| sum += i }
  puts "Sum calculated: #{sum}"
end

Output:

Sum calculated: 499500

Execution time: 0.001 seconds

🔹 Array Iteration with Yield

Build custom iteration methods using yield to process array elements. This shows how Ruby's built-in methods like each are implemented and how you can create similar functionality.

# Custom each method
def my_each(array)
  i = 0
  while i < array.length
    yield(array[i])
    i += 1
  end
end

my_each([1, 2, 3, 4]) { |num| puts "Number: #{num}" }

Output:

Number: 1

Number: 2

Number: 3

Number: 4

🧠 Test Your Knowledge

What method checks if a block was provided to a method?