Blocks, Procs, and Lambdas Explained
Blocks, procs, and lambdas are Ruby’s closure primitives — reusable chunks of code that you can pass around and execute later. They are fundamental to Ruby’s iterators, callbacks, and DSLs. Understanding these three concepts is essential for writing idiomatic Ruby code.
What is a Block?
A block is a chunk of code enclosed in do...end or braces {}. It is not an object — you cannot store it in a variable. Blocks are used with methods like each, map, and select.
[1, 2, 3].each do |num|
puts num * 2
end
# Output: 2, 4, 6
[1, 2, 3].map { |num| num * 2 }
# => [2, 4, 6]
Blocks are the most common way to iterate in Ruby. Every Ruby developer uses them daily. The two syntaxes are equivalent — do...end is typically used for multi-line blocks, while braces are used for single-line expressions.
Converting a Block to a Proc
A proc is a saved block — it is an object that you can store in a variable and call later. Use Proc.new or proc to create one. This is the key difference from blocks: procs are first-class objects.
double = Proc.new { |x| x * 2 }
double.call(5)
# => 10
# Alternative syntax
triple = proc { |x| x * 3 }
triple.call(4)
# => 12
Procs are incredibly useful when you need to pass the same block of code to multiple methods, or when you need to store a callable for later use.
Differences Between Blocks and Procs
| Feature | Block | Proc |
|---|---|---|
| Is an object? | No | Yes |
| Can be stored in variable? | No | Yes |
| Can be returned from method? | No | Yes |
| Return behavior | Returns from enclosing method | Returns from the proc itself |
The return behavior is particularly important. When a block executes return, it returns from the enclosing method. When a proc executes return, it returns from the proc itself.
Lambda Functions
A lambda is another way to create a proc, with stricter argument checking. Use the lambda keyword or the stabby syntax ->. Lambdas are procs, but with different behavior.
square = lambda { |x| x ** 2 }
square.call(3)
# => 9
# Stabby syntax (Ruby 1.9+)
cube = ->(x) { x ** 3 }
cube.call(2)
# => 8
The stabby syntax is often preferred in modern Ruby because it’s more concise and reads like mathematical notation.
Lambdas vs Procs
The most important difference is argument handling. Procs are lenient with arguments, while lambdas enforce exact argument counts.
# Proc doesn't enforce argument count
p = Proc.new { |a, b| a + b }
p.call(1, 2, 3)
# => 3 (extra argument ignored)
p.call(1)
# => nil (missing argument treated as nil)
# Lambda enforces exact argument count
l = lambda { |a, b| a + b }
l.call(1, 2, 3)
# => ArgumentError (wrong number of arguments)
l.call(1)
# => ArgumentError (wrong number of arguments)
This strict behavior makes lambdas behave more like methods, which is often what you want.
Returning from Blocks and Lambdas
The return behavior differs between procs and lambdas in a subtle but important way:
def try_proc
p = Proc.new { return "Returned from proc" }
p.call
"After proc call"
end
def try_lambda
l = lambda { return "Returned from lambda" }
l.call
"After lambda call"
end
try_proc
# => "Returned from proc" (exits the method immediately)
try_lambda
# => "After lambda call" (lambda returns to here, execution continues)
This is crucial when using closures inside methods. Procs can unexpectedly exit your method, while lambdas behave like regular method calls.
When to Use Each
Use blocks for one-off iteration with methods like each, map, and select. This is the most common pattern in Ruby.
Use procs when you need to store a callable object, particularly for callbacks, event handlers, or when passing behavior to methods that expect a proc.
Use lambdas when you need strict argument checking and want behavior similar to methods. They’re particularly useful for functional programming patterns.
Common Patterns
Storing Multiple Operations
operations = {
double: Proc.new { |x| x * 2 },
square: ->(x) { x ** 2 },
triple: proc { |x| x * 3 }
}
operations[:double].call(5)
# => 10
operations[:square].call(4)
# => 16
operations[:triple].call(3)
# => 9
This pattern is useful for strategy patterns and when you need to select behavior at runtime.
Method Arguments
def apply_operation(num, &block)
block.call(num)
end
# Pass a block directly
apply_operation(5) { |x| x + 1 }
# => 6
# Convert block to proc and pass
my_proc = Proc.new { |x| x * 2 }
apply_operation(5, &my_proc)
# => 10
The & operator converts between blocks and procs. Prefix with & to convert a proc to a block, or suffix to convert a block to a proc.
Building DSLs
Blocks are the foundation of Ruby’s famous DSL capability:
# RSpec-style DSL
RSpec.describe "My app" do
it "works" do
expect(true).to eq(true)
end
end
# HTML builder
html do
head do
title "My Page"
end
body do
h1 "Hello World"
end
end
Summary
- Blocks are temporary, inline code chunks used with iterator methods
- Procs are saved blocks — objects you can store and reuse
- Lambdas are procs with strict argument checking and method-like return behavior
Master these three concepts to write powerful, idiomatic Ruby code.
See Also
- yield — yield execution to a block
- Proc vs Lambda — key differences
- Blocks and Iterators — in the Ruby Fundamentals series