yield and Block Arguments

Published by Programming Line in Ruby. Last updated .

In Ruby, methods can receive an implicit chunk of code called a block, and the yield keyword is how a method reaches back and runs that block. It’s the mechanism behind dozens of familiar methods like each, map, and times — each of those is just a plain method that calls yield internally. Understanding yield means understanding how Ruby lets you write methods that accept custom behavior from the caller without ever declaring a block as a formal parameter.

Overview: How yield Works

Every method in Ruby can optionally be called with a block attached — a chunk of code wrapped in do...end or curly braces that follows the method call. Unlike a regular argument, a block is not passed as part of the parameter list; it rides along invisibly, and the method decides whether and when to run it. The keyword yield is what actually runs it: when Ruby’s interpreter hits yield inside a method body, it pauses the method, executes the attached block, and then resumes the method with whatever value the block returned.

This is the mechanism behind nearly every Enumerable method you already use. [1, 2, 3].each { |n| puts n } is not special syntax — each is an ordinary method (implemented in C for the built-in classes, or in plain Ruby if you wrote it yourself) that loops over the array and calls yield(element) for each item. The block you write is simply what runs each time yield fires inside that method.

Because a block isn’t automatically an object, you can’t store it in a variable or pass it along to another method by name unless you capture it explicitly. Ruby gives you two related tools for that: block_given?, which lets a method check whether it was called with a block at all (calling yield with no block raises LocalJumpError), and an explicit &block parameter, which captures the block as a real Proc object bound to a local variable so you can call it with .call, store it, or forward it to another method.

yield is also an expression, not a statement — it evaluates to whatever the block’s last expression returns. That return value can be used just like any other value: assigned to a variable, passed to another method, or made the return value of the enclosing method itself (Ruby methods always return the value of their last-evaluated expression, with no explicit return required).

Syntax

def method_name(*args)
  # ... some setup code ...
  result = yield(optional_arguments)
  # ... result is whatever the block returned ...
end

# Calling with a single-line block:
method_name(1, 2) { |param1, param2| param1 + param2 }

# Calling with a multi-line block:
method_name(1, 2) do |param1, param2|
  param1 + param2
end
  • yield — calls the block that was passed to the current method, with no arguments.
  • yield(arg1, arg2, ...) — calls the block, passing values that are bound to the block’s own parameters (declared between the pipes, e.g. |param1, param2|).
  • block_given? — a boolean check, usually used to guard a yield so the method doesn’t raise an error when called without a block.
  • An explicit &block parameter — turns the implicit block into a named, callable Proc object.
Construct Meaning
yield Invoke the block passed to the current method, with no arguments
yield(args) Invoke the block, passing one or more arguments to its parameters
block_given? Returns true if the current method was called with a block, false otherwise
&block Captures the passed block as an explicit Proc object bound to the parameter name

Examples

Example 1: A basic yield

def greet
  puts "Before yield"
  yield
  puts "After yield"
end

greet { puts "Hello from block!" }

Output:

Before yield
Hello from block!
After yield

greet prints its first line, then hits yield, which hands control to the block attached to the call — the block prints its own line — and then control returns to greet, which prints its final line. The block runs exactly where the yield appears, not before or after the method.

Example 2: Passing arguments with yield

def repeat(n)
  n.times do |i|
    yield i
  end
end

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

Output:

Iteration 0
Iteration 1
Iteration 2

yield i passes a value to the block, which binds it to its own parameter i. Note that the yield here sits inside the block passed to n.times, yet it still calls the block passed to repeat — a detail explored further below.

Example 3: block_given? and yield’s return value

def calculate(a, b)
  if block_given?
    yield(a, b)
  else
    a + b
  end
end

puts calculate(4, 5)
puts calculate(4, 5) { |x, y| x * y }

Output:

9
20

The first call has no block, so block_given? is false and calculate falls back to plain addition. The second call supplies a block, so yield(a, b) runs it, and the block’s return value (4 * 5) becomes the value of the if expression — and therefore the method’s own return value, since it’s the last expression evaluated.

Example 4: Capturing the block explicitly with &block

def process_items(items, &block)
  items.each do |item|
    block.call(item.upcase)
  end
end

process_items(["ruby", "python", "go"]) { |name| puts "Language: #{name}" }

Output:

Language: RUBY
Language: PYTHON
Language: GO

Here the block is captured explicitly as &block, turning it into a real Proc object stored in the local variable block. That lets the method call it with .call from inside a nested each block, transform the argument first, and invoke it as many times as needed — something a bare yield can also do, but an explicit capture is useful when you need to pass the block along or store it for later.

How it works, step by step

Walking through repeat(3) { |i| puts "Iteration #{i}" } from Example 2:

  1. Ruby calls repeat(3). The block { |i| puts "Iteration #{i}" } is attached to this call but not evaluated yet — it’s simply remembered by the method invocation.
  2. Inside repeat, n.times runs, which itself calls a block three times: once for i = 0, once for i = 1, once for i = 2.
  3. On each pass, the line yield i executes. Ruby suspends the current method and jumps to the block that was passed to repeat (not to times), binding its parameter i to the value passed.
  4. The block body runs — puts "Iteration #{i}" — and prints a line.
  5. Control returns to right after the yield i call inside the times block, which then moves on to the next iteration.
  6. Once n.times has run all three iterations, repeat has no more code to execute and returns.

Notice the important detail in step 3: even though yield i is written inside the block passed to times, it still refers to the block passed to repeat. A yield always calls the block belonging to the nearest enclosing method definition, never a block belonging to another block.

Common Mistakes

Mistake 1: Calling yield without checking for a block

If a method calls yield but is invoked without a block, Ruby raises LocalJumpError. This is a common surprise for anyone used to languages where an optional callback simply does nothing if it’s missing.

def shout
  yield.upcase
end

shout
# LocalJumpError: no block given (yield)

The fix is to guard the yield with block_given? so the method behaves sensibly either way:

def shout
  return "no block given" unless block_given?
  yield.upcase
end

puts shout
puts shout { "hello" }

Output:

no block given
HELLO

Mistake 2: Forgetting that yield’s return value is discarded unless it’s the last expression

A Ruby method always returns the value of its last evaluated expression, so a yield call in the middle of a method body throws away whatever the block returned — the method’s own trailing expression wins instead.

def build_message(name)
  yield(name)
  "Message built"
end

result = build_message("Ruby") { |n| "Hello, #{n}!" }
puts result

Output:

Message built

The block’s return value, "Hello, Ruby!", is computed and then silently discarded, because yield(name) isn’t the last line of the method. Removing the trailing string literal lets yield‘s result become the method’s own return value:

def build_message(name)
  yield(name)
end

result = build_message("Ruby") { |n| "Hello, #{n}!" }
puts result

Output:

Hello, Ruby!

Best Practices

  • Guard every yield with block_given? unless a block is genuinely required for the method to make sense — a clear fallback or a deliberate ArgumentError beats a cryptic LocalJumpError.
  • Only capture the block explicitly with &block when you need to pass it along to another method, store it, or call it more than once — a bare yield is more idiomatic and reads more clearly when you’re just invoking it inline.
  • Remember that yield passes arguments to the block loosely: extra arguments are ignored and missing ones become nil, the same lenient arity rules that apply to proc, not the strict rules of lambda.
  • Be deliberate about a method’s last expression whenever it also calls yield — decide explicitly whether the method should return the block’s result or something else, rather than letting it happen by accident.
  • Prefer yield and block_given? over older idioms like calling Proc.new with no arguments to grab an implicit block — it’s clearer and matches current Ruby style guides.

Practice Exercises

  1. Write a method with_timing that takes no arguments, prints "Starting...", calls yield, then prints "Done.". Call it with a block that prints "Working", and confirm the three lines print in the right order.
  2. Write a method safe_divide(a, b) that yields the result of a / b to a block if b is not zero, and yields the string "undefined" if b is zero. Use block_given? so calling it without a block returns nil instead of raising an error. Hint: you’ll need an if/else around the yield call.
  3. Write a method transform_all(array, &block) that builds and returns a new array by calling block.call on every element of array. Test it with transform_all([1, 2, 3]) { |n| n * n } and confirm the result is [1, 4, 9].

Summary

  • yield invokes the block attached to the current method call; it’s the mechanism behind each, map, and every other block-taking method in Ruby.
  • A block is not an object by default — it can’t be stored or passed along unless captured explicitly with an &block parameter, which turns it into a real Proc.
  • block_given? lets a method check for a block before calling yield, avoiding LocalJumpError when no block was passed.
  • yield(args) passes values to the block’s parameters leniently — extra args are dropped, missing ones become nil.
  • yield is an expression: it evaluates to the block’s return value, which becomes the method’s own return value only if it’s the last thing the method evaluates.
  • yield always calls the block belonging to the enclosing method definition, even when written inside a nested block.