Blocks, Procs & Lambdas
Master Ruby blocks, the yield keyword, Proc objects, and lambdas — and understand the real differences between procs and lambdas.
Introduction
Blocks, procs, and lambdas are Ruby's answer to "code as a value" — the same underlying idea as closures in Lua or JavaScript, expressed through three closely related but distinct Ruby constructs. You've already used blocks throughout this course (in .each, for instance) without a full explanation; this lesson gives you one.
- What a block is, and its two equivalent syntaxes ({ } and do...end).
- How yield calls the block passed to a method.
- What Proc and lambda objects are, and how to create each.
- The real, practical differences between procs and lambdas.
Blocks: { } and do...end
A block is a chunk of code attached to a method call — you've already used one every time you called .each. Ruby offers two interchangeable syntaxes: curly braces for short, single-line blocks, and do...end for longer, multi-line ones.
[1, 2, 3].each { |n| puts n * 2 }
[1, 2, 3].each do |n| doubled = n * 2 puts doubledendClick Run to see what this code prints.
yield: Calling a Block from a Method
Use case: inside any method, yield calls whatever block was passed to it at the call site — this is how methods like .each are actually implemented under the hood, and you can write your own methods that accept blocks the exact same way.
def repeat(times) times.times do |i| yield i endend
repeat(3) { |i| puts "Iteration #{i}" }Click Run to see what this code prints.
Procs: Blocks as Objects
Use case: a Proc turns a block into a real object you can store in a variable, pass around, and call later — useful when a chunk of code needs to outlive a single method call, or be reused in multiple places.
double = Proc.new { |n| n * 2 }
puts double.call(5) # 10puts double.(5) # 10 -- shorthand call syntaxputs double[5] # 10 -- another equivalent shorthandClick Run to see what this code prints.
Lambdas
A lambda is a special kind of Proc, created with lambda { ... } or the shorter -> (...) { ... } "stabby lambda" syntax — functionally very close to a Proc, but with two important behavioral differences covered next.
square = ->(n) { n * n }
puts square.call(6) # 36puts square.(6) # 36Click Run to see what this code prints.
Procs vs Lambdas: The Real Differences
| Aspect | Proc | Lambda |
|---|---|---|
| Argument count checking | Lenient — extra or missing arguments are silently ignored/nil | Strict — calling with the wrong number of arguments raises an error |
| return keyword inside it | Returns from the enclosing method entirely | Returns only from the lambda itself, like a normal method |
| Creation syntax | Proc.new { ... } | lambda { ... } or ->(args) { ... } |
def proc_test my_proc = Proc.new { return "from the proc" } my_proc.call "This line never runs"end
def lambda_test my_lambda = lambda { return "from the lambda" } my_lambda.call "This line DOES run"end
puts proc_testputs lambda_testClick Run to see what this code prints.
A proc's return exits the entire enclosing method immediately, which can be surprising if you weren't expecting it. A lambda behaves like a normal method call — its return only exits the lambda itself. This is the single most important practical difference between the two.
Common Mistakes
- Using return inside a Proc without realizing it exits the entire enclosing method, not just the proc.
- Assuming a Proc and a lambda are fully interchangeable — their argument-checking strictness and return behavior genuinely differ.
- Forgetting yield requires a block to actually be passed at the call site, unless you guard it with block_given?.
Best Practices
- Default to lambdas over Proc.new for most standalone reusable code blocks, since their stricter, more predictable behavior avoids surprises.
- Use { } for short, single-expression blocks, and do...end for longer, multi-line ones — a widely followed style convention.
- Check block_given? inside a method before calling yield if the block is genuinely optional.
Frequently Asked Questions
Blocks, procs, and lambdas are all closures in the general sense — they capture and remember variables from their surrounding scope, the same underlying concept covered for Lua elsewhere on this platform.
Yes — prefixing a parameter with & (like def run(&block)) captures the passed block as an explicit Proc object you can store, pass along, or call with .call, instead of relying on the implicit yield.
A block is lightweight and implicit — perfect for the extremely common case of a method taking exactly one block. A Proc is that same idea promoted to a full, storable, reusable object once you need more flexibility than a single inline block offers.
Key Takeaways
- A block is a chunk of code attached to a method call, written with { } or do...end.
- yield calls the block passed to the current method.
- A Proc turns a block into a reusable, storable object; a lambda is a stricter variant of a Proc.
- Lambdas check argument counts strictly and return only from themselves; Procs are lenient and return exits the whole enclosing method.
Summary
Blocks, procs, and lambdas are a big part of what makes Ruby code feel so expressive — passing behavior around as naturally as passing any other value. Next, you'll cover classes and modules, Ruby's tools for organizing and sharing object-oriented code.