Skip to content

Ruby Blocks and Procs — yield Proc.new Call and Block Passing Explained

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Ruby Blocks and Procs. We cover key concepts, practical examples, and best practices to help you master this topic.

Ruby blocks are anonymous code blocks passed to methods using yield or &block, Proc objects encapsulate blocks as reusable objects, and the & operator converts between blocks and procs.

What You'll Learn

  • Defining and using blocks with yield
  • Converting blocks to Proc objects with &block
  • Creating Proc objects with Proc.new
  • Callable objects and closures

Why It Matters

Blocks are Ruby's most distinctive feature and the foundation of its Iterator pattern. Every each, map, and select call uses a block. Durga Antivirus Pro uses blocks for file scanning callbacks and progress reporting. DodaZIP uses blocks for compression pipeline stages. Understanding blocks is essential for writing idiomatic Ruby.

Real-World Use

Rails uses blocks extensively: before_save { ... }, validates :name, presence: true (blockless), link_to "Home", root_path, class: "nav-link". Sinatra routes: get '/hello' do ... end. Every Ruby developer encounters blocks daily.

flowchart LR
    A["Blocks & Procs"] --> B["Yield"]
    B --> C["Block Arguments"]
    C --> D["&block"]
    D --> E["Proc Objects"]
    E --> F["Lambdas"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style F fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Blocks

A block is a chunk of code between do...end or { }:

# do...end style (for multi-line)
[1, 2, 3].each do |n|
  puts n * 2
end

# { } style (for single-line)
[1, 2, 3].each { |n| puts n * 2 }

Using Yield

yield calls the block passed to the method:

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

greet { puts "Hello from block" }
# Before yield
# Hello from block
# After yield

Yielding with Arguments

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

puts calculate(5, 3) { |x, y| x + y }
# Result: 8

puts calculate(5, 3) { |x, y| x * y }
# Result: 15

Checking If a Block Was Given

def greet
  if block_given?
    yield
  else
    puts "No block provided"
  end
end

greet { puts "Hello" }   # Hello
greet                      # No block provided

The &block Parameter

Capture a block as a named Proc parameter using &:

def repeat(times, &block)
  times.times { block.call }
end

repeat(3) { puts "Hello" }
# Hello
# Hello
# Hello

Converting Between Blocks and Procs

# Block to Proc with &
def run_proc(&my_block)
  puts "Running block: #{my_block.call}"
end

run_proc { "Hello from block" }
# Running block: Hello from block

# Proc to Block with &
my_proc = Proc.new { "Hello from proc" }
[1, 2, 3].each(&my_proc)  # No output (each doesn't print)

Proc Objects

A Proc is a block turned into an object:

# Creating a Proc
my_proc = Proc.new { |name| "Hello, #{name}!" }
another_proc = proc { |name| "Hi, #{name}!" }  # Kernel#proc

# Calling a Proc
puts my_proc.call("Alice")   # Hello, Alice!
puts my_proc["Bob"]          # Hello, Bob!
puts my_proc.("Charlie")     # Hello, Charlie!
puts my_proc === "Dave"      # Hello, Dave!

Proc and Return

def test_proc
  my_proc = Proc.new { return "from proc" }
  my_proc.call
  "from method"  # Never reached
end

puts test_proc  # from proc

Procs return from the enclosing method! This is different from lambdas.

Proc with Each

double = Proc.new { |n| n * 2 }
puts [1, 2, 3].map(&double).inspect
# [2, 4, 6]

Block as a Closure

Blocks capture the surrounding scope:

def make_counter
  count = 0
  Proc.new { count += 1 }
end

counter = make_counter
puts counter.call  # 1
puts counter.call  # 2
puts counter.call  # 3

another_counter = make_counter
puts another_counter.call  # 1 (independent)

Symbol to Proc

A common Ruby idiom is converting a symbol to a proc:

names = ["alice", "bob", "charlie"]

# Long form
puts names.map { |name| name.upcase }.inspect
# ["ALICE", "BOB", "CHARLIE"]

# Short form with Symbol#to_proc
puts names.map(&:upcase).inspect
# ["ALICE", "BOB", "CHARLIE"]

# Works with any method
puts names.map(&:length).inspect
# [5, 3, 7]
puts names.map(&:reverse).inspect
# ["ecila", "bob", "eirahlc"]

Passing Multiple Blocks

Technically, you can only pass one block per method call, but you can pass additional procs as regular arguments:

def process(data, success_proc, error_proc)
  if data.valid?
    success_proc.call(data)
  else
    error_proc.call(data)
  end
end

success = Proc.new { |d| puts "Processed #{d}" }
error = Proc.new { |d| puts "Error with #{d}" }

data = "some data"
process(data, success, error)

The tap Method

Kernel#tap yields the receiver to a block and returns the receiver:

result = [1, 2, 3].map { |n| n * 2 }
                  .tap { |arr| puts "Mapped: #{arr.inspect}" }
                  .select { |n| n > 3 }
                  .tap { |arr| puts "Selected: #{arr.inspect}" }

# Mapped: [2, 4, 6]
# Selected: [4, 6]

Common Patterns

Iterator with Custom Logic

def filter_and_transform(array)
  result = []
  array.each do |item|
    if yield(item)  # Custom filter condition
      result << item
    end
  end
  result
end

numbers = [1, 2, 3, 4, 5, 6]
even_squares = filter_and_transform(numbers) { |n| n.even? }
puts even_squares.inspect  # [2, 4, 6]

Resource Management

def with_file(filename)
  file = File.open(filename, "r")
  yield(file)
ensure
  file.close if file
end

with_file("data.txt") do |file|
  puts file.read
end

Common Mistakes

1. Forgetting to Check block_given?

def greet
  yield  # RuntimeError if no block given!
end

def greet
  yield if block_given?  # Safe
end

2. Confusing Proc Return Behavior

def test
  p = Proc.new { return "from proc" }
  q = lambda { return "from lambda" }
  p.call  # Returns from test method!
  q.call  # Returns from lambda only
  "from method"
end

puts test  # from proc (lambda version would return "from method")

3. Using puts vs Returning from Blocks

# Block that returns value (correct for map)
[1, 2, 3].map { |n| n * 2 }

# Block that prints (correct for each)
[1, 2, 3].each { |n| puts n }

4. Forgetting & When Passing Proc to Method

my_proc = Proc.new { "hello" }

def run_block(&block)
  block.call
end

run_block(my_proc)     # WRONG — ArgumentError (expected block, got Proc)
run_block(&my_proc)    # RIGHT — & converts Proc to block

5. Overusing Complex Block Chains

# Hard to read
data.map { |x| x.strip }
    .select { |x| x.length > 0 }
    .map { |x| x.capitalize }
    .each { |x| puts x }

# Better with intermediate variables
stripped = data.map(&:strip)
non_empty = stripped.select { |x| x.length > 0 }
capitalized = non_empty.map(&:capitalize)
capitalized.each { |x| puts x }

Practice Questions

1. What does yield do in a Ruby method?

yield calls the block that was passed to the method. It can pass arguments to the block and can receive the block's return value.

2. How do you convert a block to a Proc object?

Add &block as the last parameter in the method definition. This captures the block as a Proc object. To convert a Proc to a block, use &proc_obj when calling a method.

3. What's the difference between Proc.new and lambda?

Lambdas check argument count (arity) and return from the lambda itself. Procs don't check arity and return from the enclosing method. Use lambdas when you want function-like behavior.

4. How does Symbol#to_proc work?

:upcase.to_proc creates a Proc that calls :upcase on its argument. ["hello"].map(&:upcase) is equivalent to ["hello"].map { |s| s.upcase }.

Challenge: Create a measure_time method that takes a block, runs it, and prints the execution time. Return the block's return value.

Solution
def measure_time(&block)
  start = Time.now
  result = block.call
  elapsed = Time.now - start
  puts "Completed in #{elapsed.round(4)}s"
  result
end

result = measure_time do
  sleep 0.5
  "Done"
end

puts result  # Done

Expected output:

Completed in 0.5001s
Done

FAQ

{{< faq question="What's the difference between |n| and &block?" >}} |n| is a block parameter — it receives yielded values inside the block. &block captures the entire block as a Proc. The | | syntax receives data, & captures code. {{< /faq >}}

{{< faq question="Can I yield multiple times?" >}} Yes. A method can yield multiple times, passing different arguments each time. This is how iterators work — each yields each element in sequence. {{< /faq >}}

{{< faq question="What's the difference between do...end and { }?" >}} They're functionally identical. Convention: use do...end for multi-line blocks and { } for single-line blocks. There's a subtle precedence difference — { } binds tighter than do...end. {{< /faq >}}

{{< faq question="Can I pass multiple blocks to a method?" >}} No, Ruby methods accept exactly one block. However, you can pass additional procs as regular arguments: my_method(arg1, proc1, proc2) { ... }. {{< /faq >}}

{{< faq question="What is a closure in Ruby?" >}} A closure is a block or proc that captures the surrounding variables at the time of creation, even if those variables go out of scope. This enables patterns like counters, iterators, and callbacks. {{< /faq >}}

Try It Yourself

# blocks_demo.rb

def timer
  start = Time.now
  result = yield if block_given?
  puts "Took #{(Time.now - start).round(4)}s"
  result
end

# Measure sleep
timer { sleep 0.3 }

# Create a counter closure
def make_counter(start = 0)
  count = start
  Proc.new { count += 1 }
end

counter_a = make_counter(10)
counter_b = make_counter(100)

puts "A: #{counter_a.call}, #{counter_a.call}"
puts "B: #{counter_b.call}"

# Symbol to proc
words = %w[ruby blocks procs lambda]
puts "Uppercase: #{words.map(&:upcase).join(', ')}"
puts "Lengths: #{words.map(&:length).inspect}"

Expected output:

Took 0.3001s
A: 11, 12
B: 101
Uppercase: RUBY, BLOCKS, PROCS, LAMBDA
Lengths: [4, 6, 5, 6]

What's Next

Now that you understand blocks and procs, learn about lambdas — stricter, function-like closures that differ from procs in argument handling and return behavior.

Topic Description Link
Ruby Lambdas ->, lambda, arity, closure {{< ref "13-lambdas" >}}
Ruby Mixins Comparable, Enumerable {{< ref "14-mixins" >}}
Python Lambda Functions Compare Python lambda expressions Python

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro