Understanding Blocks, Procs, and Lambdas in Ruby
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.
TL;DR
Use blocks for temporary inline behavior, procs when you want a callable object you can store or pass around, and lambdas when you want proc-like behavior with stricter argument checking. The differences are small in syntax but big in how return values and argument counts behave. If you remember only one thing, remember that a block is not an object, while both a proc and a lambda are.
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.
Blocks work best when the method owns the flow and the block only supplies the behavior. That is why they feel so natural with iterators: the method decides how many times to yield, and the block focuses on one small action at a time. Once you start thinking in those terms, blocks become less mysterious and much easier to place in the right part of the code.
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.
That difference is the part most people trip over first. A block is tied to the method that yielded to it, so return escapes the outer method. A proc is an object, which makes it easier to store and reuse, but also means its control flow is a little less predictable unless you are expecting it. That is why many Ruby codebases reach for lambdas when they want proc-like reuse without the surprise. The stricter argument checking in lambdas also helps catch mistakes earlier, because a mismatch between the number of parameters and arguments raises an error immediately rather than silently filling in nil values.
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.
Lambdas are a good choice when the callable needs to feel like a small function with a clear contract. If you pass the wrong number of arguments, Ruby should tell you immediately. That makes lambdas useful in places where the code will be passed around a lot or where argument mistakes would be expensive to debug later.
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 you are choosing between them, think about the shape of the API you want to expose. If the caller should simply hand you a tiny block of behavior, use a block. If the caller needs to store and reuse the behavior later, use a proc. If the caller needs strict argument checks and method-like flow, use a lambda. That simple rule keeps the three concepts easy to separate in practice.
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.
In day-to-day Ruby, the most important part is not memorizing every difference at once. It is knowing which tool matches the shape of the problem. Blocks fit iteration and DSLs, procs fit reusable callables, and lambdas fit small function-like pieces with explicit inputs. Once that mapping is clear, the syntax starts to feel natural instead of arbitrary.
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.
It also shows why procs and lambdas are often treated as data. Once a callable can be stored in a hash or passed through another method, Ruby code becomes more flexible without giving up readability. The important part is to keep the callable small and focused so the caller can still understand what each entry does at a glance.
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.
That conversion is handy when you want to keep one implementation and change only how it is passed around. The method still decides when to call the behavior, while the caller decides whether the behavior should be inline, stored, or reused. That separation is what makes the & operator feel natural in Ruby code.
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.
Taken together, the three constructs give you a small but expressive toolkit. Blocks keep iteration readable, procs let you move behavior through the program, and lambdas add method-like checks when the callable needs a firmer contract. Once you are comfortable with all three, you can choose the one that matches the job instead of bending the code around one syntax.
See Also
- Ruby value objects — immutable data patterns that complement closures
- Proc vs Lambda — key differences between closure types
- Blocks and Iterators — in the Ruby Fundamentals series