📦 What is a Block in Ruby?
A block in Ruby is a piece of code that is enclosed between either do...end
or curly braces {...}
. It’s like an **anonymous function** that can be passed into a method as an argument.
Blocks are **not objects themselves**, but they can be converted into Procs, which are objects. They are very commonly used in methods like each
, map
, and select
to define what action to perform on elements.
A block can accept parameters and contains logic that runs when a method yields to it. A method can call the block using the yield
keyword or by accepting an explicit &block
argument.
# Using a block with do...end
3.times do |i|
puts \"Block called with i = #{i}\"
end
# Using a block with {}
3.times { |i| puts \"Short block for i = #{i}\" }
Proc
using &block
.- Not assigned to a variable directly (unlike Procs or Lambdas)
- Automatically passed to methods that expect a block
- Uses
yield
or&block.call
to execute
In essence, blocks are a **core part of Ruby’s elegant syntax for handling iteration, callbacks, and closures**.
🔤 Syntax: do...end
vs {}
in Ruby Blocks
Ruby allows two syntaxes for writing blocks: do...end
and {...}
. Both achieve the same purpose — they define a block of code — but their usage and readability differ slightly based on context and convention.
# do...end is preferred for multi-line blocks
[1, 2, 3].each do |n|
puts \"Number: #{n}\"
end
# {} is preferred for single-line blocks
[1, 2, 3].each { |n| puts \"Number: #{n}\" }
While these are functionally equivalent, Ruby developers follow a convention:
do...end
is typically used for multi-line blocks to enhance readability.{}
is typically used for single-line blocks to keep code concise.
# Ambiguity without parentheses
puts [1, 2, 3].map { |n| n * 2 } # Good
puts ([1, 2, 3].map { |n| n * 2 }) # Safer
🧲 How yield
Works with Blocks in Ruby
The yield
keyword in Ruby is used to call a block that has been passed to a method. It allows a method to temporarily transfer control to the block, execute the code inside it, and then return back to the method.
You can think of yield
as a placeholder for the block. When the method runs and hits yield
, it executes the block’s content.
# Example with yield
def wrapper
puts \"Before yield\"
yield
puts \"After yield\"
end
wrapper { puts \"Inside the block\" }
# Output:
# Before yield
# Inside the block
# After yield
You can also pass arguments to yield
that the block will receive:
def greet
yield(\"Alice\")
end
greet { |name| puts \"Hello, #{name}!\" }
# Output:
# Hello, Alice!
- No explicit block parameter is needed when using
yield
. - If no block is given and
yield
is called, Ruby raises aLocalJumpError
.
block_given?
to check if a block was passed to avoid runtime errors.def maybe_yield
if block_given?
yield
else
puts \"No block provided.\"
end
end
🔧 What is a Proc in Ruby?
A Proc (short for Procedure) is an object in Ruby that holds a block of code. Unlike blocks, which are not objects, a Proc can be stored in a variable, passed around, and called multiple times.
Procs allow you to encapsulate logic in a reusable object and delay execution until you explicitly invoke it with .call
.
# Creating a Proc
say_hello = Proc.new { puts "Hello from a Proc!" }
# Calling a Proc
say_hello.call
# Output:
# Hello from a Proc!
- Procs are instances of the
Proc
class. - They can accept parameters like methods.
- They remember the scope in which they were defined (closure).
# Proc with parameters
greet = Proc.new { |name| puts "Hi, #{name}!" }
greet.call("Ruby") # Output: Hi, Ruby!
&
operator, just like a block!def run(proc)
proc.call
end
my_proc = Proc.new { puts "Running via method" }
run(my_proc)
🛠️ Creating and Calling a Proc in Ruby
In Ruby, there are several ways to create a Proc
. Once created, a Proc can be called using the .call
method or simply with square brackets []
.
📌 1. Using Proc.new
say_hi = Proc.new { puts "Hi there!" }
say_hi.call # Output: Hi there!
say_hi[] # Alternative way to call
📌 2. Using proc
keyword
say_hello = proc { puts "Hello!" }
say_hello.call # Output: Hello!
📌 3. With parameters
greet = Proc.new { |name| puts "Welcome, #{name}!" }
greet.call("Ruby") # Output: Welcome, Ruby!
flex_proc = Proc.new { |x, y| puts "x=#{x}, y=#{y}" }
flex_proc.call(1) # Output: x=1, y=
You can pass a Proc to a method using the &
operator, which turns it into a block:
def do_twice(&block)
block.call
block.call
end
repeat = Proc.new { puts "Run me!" }
do_twice(&repeat)
# Output:
# Run me!
# Run me!
🔐 What is a Lambda in Ruby?
A Lambda in Ruby is a special kind of Proc
object that behaves more like a method. It is used to store code that you want to execute later, just like a Proc, but with a few key differences:
- It checks the number of arguments strictly (like a method).
return
behaves differently — it exits only from the lambda, not the enclosing method.
Lambdas are often used when you need reusable logic with well-defined input validation and safe return behavior.
📌 Creating a Lambda
# Using the lambda keyword
greet = lambda { |name| puts "Hello, #{name}!" }
# Using the stabby arrow syntax
shout = ->(word) { puts word.upcase }
greet.call("Ruby") # Output: Hello, Ruby!
shout.call("lambda") # Output: LAMBDA
# Lambda with 2 parameters
add = ->(a, b) { a + b }
add.call(2, 3) # ✅ Works
add.call(1) # ❌ ArgumentError
Behind the scenes, Lambdas are also instances of the Proc
class:
puts greet.class # => Proc
puts shout.class # => Proc
⚙️ Creating and Calling a Lambda in Ruby
In Ruby, lambdas can be created using either the lambda
keyword or the concise “stabby” lambda syntax (->
). Once created, you can call them using .call
, []
, or .()
.
🔹 1. Using lambda
keyword
say_hello = lambda { puts "Hello from lambda!" }
say_hello.call # Output: Hello from lambda!
say_hello[] # Also works
say_hello.() # Also works
🔹 2. Using ->
(stabby lambda)
double = ->(n) { n * 2 }
puts double.call(5) # Output: 10
🔹 3. Strict argument checking
Lambdas behave like methods — they raise an error if you provide the wrong number of arguments:
triple = ->(x) { x * 3 }
triple.call(3) # ✅ Output: 9
triple.call # ❌ ArgumentError: wrong number of arguments (given 0, expected 1)
🔹 4. Lambdas return control cleanly
def lambda_test
lam = -> { return "from lambda" }
lam.call
return "after lambda"
end
puts lambda_test
# Output: "after lambda"
⚖️ Block vs Proc vs Lambda – Key Differences
While all three — blocks, Procs, and lambdas — deal with passing and storing code in Ruby, they have crucial differences in terms of behavior, object orientation, and control flow.
Feature | Block | Proc | Lambda |
---|---|---|---|
Is it an object? | ❌ No | ✅ Yes (Proc class) | ✅ Yes (Proc subclass) |
How it’s created | do...end or {} | Proc.new or proc | lambda or -> |
Strict argument checking | ❌ No | ❌ No | ✅ Yes |
Return behavior | Returns from method | Returns from method | Returns only from lambda |
Reusable? | ❌ No | ✅ Yes | ✅ Yes |
🔄 Return Behavior: Proc vs Lambda
One of the most critical differences between a Proc and a Lambda in Ruby is how the return
keyword behaves when used inside them. Understanding this helps you avoid unexpected control flow issues.
📌 Behavior of return
in Proc
If you use return
inside a Proc, it will try to return from the enclosing method where the Proc was defined — even if that wasn’t your intention.
def test_proc
prc = Proc.new { return "from proc" }
prc.call
return "after proc"
end
puts test_proc # Output: "from proc"
💥 As you can see, the return
inside the Proc exits the entire method early.
📌 Behavior of return
in Lambda
In contrast, a Lambda behaves like a method — return
exits only from the Lambda itself, not the surrounding method.
def test_lambda
lam = -> { return "from lambda" }
lam.call
return "after lambda"
end
puts test_lambda # Output: "after lambda"
return
to behave safely like inside a method. Avoid return
in Procs unless you’re sure about the flow.🧮 Arity (Argument Checking) Differences in Block, Proc, and Lambda
Arity refers to the number of arguments a code block, proc, or lambda expects. Ruby treats arity differently depending on whether you’re using a block, a Proc, or a lambda.
🔹 Blocks and Procs: Loose Argument Checking
Procs and blocks are lenient — they don’t raise errors if the wrong number of arguments are passed.
my_proc = Proc.new { |x, y| puts "x=#{x}, y=#{y}" }
my_proc.call(1, 2) # Output: x=1, y=2
my_proc.call(1) # Output: x=1, y=
my_proc.call # Output: x=, y=
🔹 Lambda: Strict Argument Checking
Lambdas behave like methods — if you pass too many or too few arguments, Ruby raises an error.
my_lambda = ->(x, y) { puts "x=#{x}, y=#{y}" }
my_lambda.call(1, 2) # ✅ Output: x=1, y=2
my_lambda.call(1) # ❌ ArgumentError
my_lambda.call # ❌ ArgumentError
🔄 Use of &block
to Convert Block to Proc
In Ruby, the &block
syntax in method definitions is a way to capture an implicit block and convert it into an explicit Proc object. This allows you to store, pass, or call the block later.
🔹 How it works
def greet(&block)
puts "Before block"
block.call if block
puts "After block"
end
greet { puts "Hello from block!" }
# Output:
# Before block
# Hello from block!
# After block
🔹 Passing block as Proc to another method
You can also forward a block to another method using the &
operator:
def execute(&block)
do_something(&block)
end
def do_something
yield
end
execute { puts "Forwarded block!" }
🔹 Convert a Proc to a block
You can also convert a Proc
into a block by prefixing it with &
:
my_proc = Proc.new { puts "Proc to block!" }
def call_block(&block)
block.call
end
call_block(&my_proc)
&block
when you want to reuse a block multiple times or pass it around explicitly like any other variable.🧠 When to Use Block, Proc, or Lambda
Choosing between a block, proc, or lambda depends on what behavior you need — inline execution, reusable logic, or strict method-like functions.
🔹 Use a Block when:
- You’re writing simple, one-off behavior (like iterating).
- You want implicit behavior with methods like
each
,map
, orselect
. - You don’t need to reuse the logic elsewhere.
[1, 2, 3].each do |n|
puts n * 2
end
🔹 Use a Proc when:
- You want to store code in a variable and reuse it.
- You want flexible argument behavior (loose arity).
- You’re passing behavior into multiple methods or callbacks.
double = Proc.new { |n| n * 2 }
puts double.call(5) # 10
puts double.call # nil
🔹 Use a Lambda when:
- You want a method-like function with strict argument checking.
- You want predictable return behavior (exits only from lambda).
- You need to define small reusable functions (e.g., filters).
square = ->(x) { x ** 2 }
puts square.call(4) # 16
puts square.call # ArgumentError
🧪 10 Real Use Case Examples
- Block: Custom logger
def with_logging puts "Start" yield puts "End" end with_logging { puts "Doing work..." }
- Block: Wrapping database transactions
ActiveRecord::Base.transaction do # multiple DB operations end
- Proc: Callback reuse
callback = Proc.new { puts "Running callback!" } 3.times(&callback)
- Proc: Optional behavior
def maybe(&block) block.call if block end maybe { puts "Only run if block passed" }
- Lambda: Functional filtering
filter = ->(x) { x > 5 } puts [2, 4, 6, 8].select(&filter) # [6, 8]
- Lambda: Data validation
check_name = ->(name) { raise "Too short!" if name.length < 3 } check_name.call("Jo") # Raises error
- Proc: Retry logic wrapper
retryable = Proc.new do puts "Trying..." raise if rand > 0.5 end 3.times { retryable.call rescue puts "Failed, retrying..." }
- Block: Benchmarking performance
def benchmark start = Time.now yield puts "Time: #{Time.now - start}" end benchmark { sleep(1) }
- Lambda: Custom tax calculator
calculate_tax = ->(amount) { amount * 0.13 } puts calculate_tax.call(1000) # 130.0
- Proc: Pass behavior to a method
def apply_twice(proc) proc.call proc.call end say_hi = Proc.new { puts "Hi!" } apply_twice(say_hi)
block
for inline operations, proc
for reusable or flexible behaviors, and lambda
for strict control like methods.⚠️ Common Mistakes and Gotchas in Blocks, Procs, and Lambdas
Ruby offers flexibility with blocks, procs, and lambdas — but that also leads to confusing behavior if you're not careful. Here are the most common pitfalls you should avoid:
1. ❌ Assuming Proc and Lambda Behave the Same
They look similar but behave very differently:
- Proc has loose argument checking
- Lambda is strict about arity
p = Proc.new { |x, y| puts "#{x}, #{y}" }
p.call(1) # No error, prints: 1,
l = ->(x, y) { puts "#{x}, #{y}" }
l.call(1) # ArgumentError!
2. ❌ Unexpected Return Behavior
Returning from a proc exits the enclosing method. Returning from a lambda exits only the lambda:
def test_proc
p = Proc.new { return "from proc" }
p.call
return "after proc"
end
puts test_proc # => "from proc"
def test_lambda
l = -> { return "from lambda" }
l.call
return "after lambda"
end
puts test_lambda # => "after lambda"
3. ❌ Forgetting to Use yield
with Blocks
Defining a block but not calling yield
means the block won't run:
def greet
puts "Hello"
# yield is missing!
puts "Goodbye"
end
greet { puts "Inside block" }
# Output: Hello \n Goodbye
4. ❌ Not Checking if Block Exists
Calling yield
without a block leads to an error. Use block_given?
to prevent this:
def greet
yield if block_given?
end
greet # ✅ No error
5. ❌ Forgetting &
When Passing Procs
When passing a Proc as a block, don’t forget the &
:
my_proc = Proc.new { puts "Hello" }
def run(&block)
block.call
end
run(&my_proc) # ✅ Correct
run(my_proc) # ❌ Error: wrong number of arguments
🧪 Block – 3 Case Studies
Blocks are one of the most idiomatic and powerful features in Ruby. They are widely used in iterators, custom method wrappers, and domain-specific languages (DSLs). Below are 3 real case studies showcasing how and why to use blocks in practical Ruby code.
🔹 Case Study 1: Iteration with Blocks
Description: Blocks are commonly passed to enumerator methods like each
, map
, and select
.
[1, 2, 3].each { |n| puts n * 2 }
Why: Cleaner and concise syntax for quick inline logic during iteration. Enhances readability for small operations.
🔹 Case Study 2: Custom Method Wrapping
Description: Blocks let you wrap logic dynamically inside methods. This is useful for things like logging, benchmarking, and error handling.
def with_logging
puts "Started..."
yield
puts "Finished."
end
with_logging { puts "Running main logic..." }
Why: Reusable wrapper pattern without hardcoding behavior. Perfect for Rails callbacks or decorators.
🔹 Case Study 3: Building DSLs (Domain Specific Languages)
Description: Ruby gems like RSpec, Capybara, and Rails itself use blocks to allow expressive, readable syntax in DSLs.
describe "Calculator" do
it "adds numbers" do
expect(1 + 2).to eq(3)
end
end
Why: Blocks allow structured yet flexible control flows that make DSLs feel natural and clean to read and write.
🧪 Proc – 3 Case Studies
Procs allow code blocks to be stored in variables and reused, passed around, or deferred. This makes them great for decoupling logic and achieving clean abstraction. Here are three real-world examples:
🔹 Case Study 1: Storing and Reusing Logic
Description: Define a Proc once and reuse it wherever needed, reducing code duplication.
square = Proc.new { |n| n * n }
puts square.call(3) # => 9
puts square.call(5) # => 25
Why: Encapsulates logic into reusable objects, helpful when the same operation is needed in many places.
🔹 Case Study 2: Passing Logic to Methods
Description: Procs can be passed as arguments to methods using &
to be invoked as blocks.
printer = Proc.new { |name| puts "Hello, #{name}" }
def greet(&block)
block.call("Ruby")
end
greet(&printer)
Why: Makes your method more dynamic — it can execute different logic based on the Proc passed in.
🔹 Case Study 3: Closures and Persistent Scope
Description: Procs remember the scope where they were defined — including local variables.
def multiplier(factor)
Proc.new { |n| n * factor }
end
double = multiplier(2)
puts double.call(5) # => 10
triple = multiplier(3)
puts triple.call(5) # => 15
Why: Enables creation of powerful closure-based utilities — similar to function factories in JavaScript or Python.
🧪 Lambda – 3 Case Studies
Lambdas are similar to Procs but stricter in behavior. They enforce argument count (arity) and return control only within themselves, making them safer and more predictable in many use cases. Below are three practical scenarios where lambdas shine.
🔹 Case Study 1: Safe Argument Checking
Description: Lambda will raise an error if the number of arguments passed does not match expected count.
add = ->(a, b) { a + b }
puts add.call(2, 3) # ✅ 5
puts add.call(2) # ❌ ArgumentError
Why: Better for methods that need strict contracts, especially in API logic or form validation pipelines.
🔹 Case Study 2: Returning from Within a Lambda
Description: Lambdas return only from themselves — they don’t break out of the method they’re in.
def run
l = -> { return "from lambda" }
l.call
"after lambda"
end
puts run # => "after lambda"
Why: Prevents accidental early return from the containing method, unlike Procs.
🔹 Case Study 3: Functional Pipelines
Description: Lambdas are perfect for chaining logic in functional-style programming.
capitalize = ->(s) { s.capitalize }
add_suffix = ->(s) { "#{s}!" }
def run_pipeline(str, *steps)
steps.reduce(str) { |val, step| step.call(val) }
end
puts run_pipeline("hello", capitalize, add_suffix) # => "Hello!"
Why: Predictable and composable — ideal for chained transformations and clean functional design.
💼 Interview Questions & Answers – Ruby Blocks, Procs, and Lambdas
Below are commonly asked interview questions that test your understanding of how blocks, procs, and lambdas work in Ruby. Each includes a detailed explanation and code example where applicable.
1. What is a block in Ruby?
A block is an anonymous piece of code passed to a method. It can be defined using do...end
or { }
. It's not an object, but it can be converted into one using &
to become a Proc.
[1, 2, 3].each { |n| puts n }
2. What's the difference between a Proc and a Lambda?
Both are objects of class Proc
, but:
- Lambdas enforce strict arity (argument count), Procs do not.
- Lambdas return only from themselves, Procs return from the enclosing method.
3. How do you convert a block to a Proc?
You can use &block
in method definitions to capture a block and treat it like a Proc object.
def greet(&block)
block.call if block
end
4. What is the return behavior difference between Proc and Lambda?
A Proc returns from the entire method it’s defined in, whereas a Lambda returns only from itself.
def demo
p = Proc.new { return "From Proc" }
p.call
return "After Proc"
end
puts demo # => "From Proc"
5. How does yield work in Ruby?
yield
is used to execute a block passed to a method. If no block is passed, calling yield
raises an error unless handled.
def test
yield if block_given?
end
6. Can you pass multiple blocks to a method?
No, Ruby allows only one implicit block per method. However, you can simulate multiple blocks by passing multiple Procs or Lambdas.
7. How do you define a Lambda?
You can define it using ->
or lambda
keyword:
l = ->(x) { x * 2 }
# or
l = lambda { |x| x * 2 }
8. Can a Proc take variable arguments?
Yes. A Proc is lenient with arguments, and will not raise an error if arguments are missing or extra.
p = Proc.new { |a, b| puts "a=#{a}, b=#{b}" }
p.call(1) # b is nil
p.call(1, 2, 3) # extra ignored
9. Why would you use a block over a Proc?
Blocks are more idiomatic and convenient for short, inline logic. Procs are better when you need to reuse logic or pass it between methods explicitly.
10. What is the difference between block_given?
and &block
?
block_given?
checks if a block was passed to the method. &block
captures the block into a Proc object for more flexible use.
def example(&block)
puts "Has block: #{block_given?}"
block.call if block
end
🌐 External Resources
Dive deeper into Ruby blocks, Procs, and Lambdas with these high-quality external references:
- 📄 Ruby Official Documentation – Proc Class
The most accurate and up-to-date reference for Proc methods and behavior. - 📘 RubyGuides – Procs & Lambdas Explained
A clear and beginner-friendly explanation of the differences between blocks, procs, and lambdas with diagrams. - 💬 Stack Overflow Thread – Difference Between Block, Proc, and Lambda
Practical community-based discussion with real-world scenarios and edge cases.
Learn more about Rails setup