Procs & Lambdas
Reviewed & published by Brayan K
Ruby is a dynamic, beginner-friendly programming language, and procs and lambdas let you store a chunk of code in a variable — turning behaviour itself into data you can pass around and call later.
Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll create procs and lambdas, call them, understand the differences, and convert them back into blocks with &.
What You'll Learn in This Lesson
- Storing code in a Proc object
- Writing lambdas with the -> syntax
- Calling with .call, .(), and []
- How lambdas check arguments strictly
- How return differs in procs vs lambdas
- Converting to/from blocks with &
1️⃣ Creating and Calling Procs and Lambdas
A block isn't an object — but a Proc is. Build one with Proc.new { ... } and run it with .call. A lambda is a stricter cousin, written most cleanly with the stabby -> syntax. Because both are objects, you can store them in arrays and hashes.
# A block isn't an object — but a Proc is. It stores code in a variable.
double = Proc.new { |n| n * 2 }
puts double.call(5) # -> 10 (.call runs it)
puts double.call(21) # -> 42
# A lambda is a special, stricter kind of Proc.
# The -> syntax (a "stabby lambda") is the modern way to write one.
square = ->(n) { n * n }
puts square.call(4) # -> 16
puts square.(4) # -> 16 (.() is shorthand for .call)
puts square[4] # -> 16 ([] also calls it)
# Because they are objects, you can store them in arrays and pass them around.
ops = [double, square]
puts ops[0].call(10) # -> 20
puts ops[1].call(10) # -> 1002️⃣ How They Differ (Arguments and return)
Two real differences matter. A lambda is strict about argument count; a proc is lenient. And return inside a lambda exits only the lambda, while inside a proc it tries to exit the surrounding method. The & operator converts a stored callable into a block when a method expects one.
# Difference 1: ARGUMENT CHECKING.
# A lambda is STRICT about argument count; a proc is lenient.
my_lambda = ->(a, b) { "#{a}-#{b}" }
my_proc = Proc.new { |a, b| "#{a}-#{b}" }
puts my_proc.call(1) # -> 1- (missing arg becomes nil, no error)
# my_lambda.call(1) # would raise ArgumentError (strict!)
# Difference 2: how RETURN behaves.
def with_lambda
l = -> { return 10 }
l.call # return only exits the lambda
20 # ...so the method continues to here
end
puts with_lambda # -> 20
# Pass a lambda into a method that yields, using &:
def run(&block)
block.call(3)
end
puts run(&->(x) { x + 100 }) # -> 103Your turn. Build a calculator from lambdas stored in a hash. A :divide lambda is added for you — complete the TODO calls and predict the output, then run it.
# 🎯 YOUR TURN — replace each TODO, then run with: ruby calc.rb
# Build a tiny calculator from lambdas stored in a hash.
operations = {
add: ->(a, b) { a + b },
subtract: ->(a, b) { a - b },
multiply: ->(a, b) { a * b } # 👉 TODO: add a :divide lambda below
}
# Add a divide lambda (use to_f to keep decimals)
operations[:divide] = ->(a, b) { a.to_f / b }
# Call them by key
puts operations[:add].call(4, 6) # -> 10
puts operations[:multiply].call(3, 5) # -> 15
puts operations[:divide].call(7, 2) # 👉 TODO: predict this, then run
# ✅ Expected output:
# 10
# 15
# 3.5Mini-Challenge: A Discount Applier
Create a lambda and apply it across an array with map(&lambda) — the bridge from callable to block. Run with ruby discount.rb.
# 🎯 MINI-CHALLENGE: a discount applier.
# Save as discount.rb and run with: ruby discount.rb
#
# 1. Create a lambda "ten_percent" that takes a price and returns price * 0.9.
# 2. Create an array of prices, e.g. [100, 50, 20].
# 3. Use .map(&ten_percent) to apply the lambda to every price.
# (The & turns a lambda into the block map expects.)
# 4. Print the discounted prices.
#
# ✅ Example output:
# [90.0, 45.0, 18.0]
# your code hereCommon Errors (and the fix)
- Forgetting .call — Writing square alone refers to the lambda; run it with square.call(4).
- "wrong number of arguments" from a lambda — Lambdas are strict. Pass exactly the declared count, or use a proc.
- Unexpected method exit from a proc — A return inside a proc can jump out of the whole method. Prefer a lambda.
- Passing a lambda where a block is expected — Convert it with &: arr.map(&my_lambda).
- Stabby syntax typos — It's ->(x) { x }; the parentheses hold the parameters, the braces hold the body.
Pro Tips
- 💡 Prefer lambdas: their strict args and predictable return catch bugs earlier.
- 💡 Symbol-to-proc: ["a","b"].map(&:upcase) is a slick shorthand for calling a method on each item.
- 💡 Compose lambdas: (f >> g) runs f then g; (f << g) runs g then f.
📋 Quick Reference — Procs & Lambdas
| Concept | Ruby Syntax |
|---|---|
| Proc | Proc.new { |x| x } |
| Lambda | ->(x) { x } |
| Call | f.call(3) |
| Call shorthand | f.(3) / f[3] |
| To block | arr.map(&f) |
| Capture block | def m(&blk) |
🎉 Lesson Complete!
- ✅ A Proc/lambda stores code in a variable you can .call
- ✅ Lambdas use the stabby -> syntax
- ✅ Lambdas check arguments strictly; procs are lenient
- ✅ return behaves differently in each
- ✅ & converts a callable into a block (and back)
Practice quiz
How do you run a stored Proc or lambda?
- call it directly like a method
- with .run
- with .call
- with .exec
Answer: with .call. Use .call to invoke a Proc or lambda, e.g. square.call(4).
Which is the modern stabby-lambda syntax?
- ->(n) { n }
- lambda { |n| n }
- proc { |n| n }
- def(n) { n }
Answer: ->(n) { n }. The stabby form ->(n) { n } is the modern way to write a lambda.
How does a lambda treat the wrong number of arguments?
- Ignores extras, nil for missing
- Returns nil
- Converts them to an array
- Raises ArgumentError
Answer: Raises ArgumentError. Lambdas are strict about argument count and raise ArgumentError on a mismatch.
How does a Proc treat a missing argument?
- Raises ArgumentError
- Binds it to nil
- Returns false
- Skips the call
Answer: Binds it to nil. Procs are lenient: a missing argument simply becomes nil, no error raised.
Where does return jump inside a lambda?
- Exits only the lambda
- Exits the enclosing method
- Raises an error
- Exits the whole program
Answer: Exits only the lambda. return inside a lambda exits only the lambda, so the surrounding method continues.
Which are all valid ways to call a lambda named sq?
- sq(4)
- sq->4
- sq.call(4), sq.(4), sq[4]
- call sq, 4
Answer: sq.call(4), sq.(4), sq[4]. sq.call(4), sq.(4), and sq[4] all invoke the lambda.
What does the & do in arr.map(&my_lambda)?
- Doubles the lambda
- Converts the lambda into a block
- Calls it twice
- Negates it
Answer: Converts the lambda into a block. & converts a stored callable into the block the method expects.
Is a bare block (without storing it) an object you can pass around?
- Yes, always
- Only with lambda
- Only in Ruby 3
- No, you must wrap it in a Proc/lambda first
Answer: No, you must wrap it in a Proc/lambda first. A block isn't an object; wrap it in a Proc or lambda to store and pass it.
What does ->(n){ n * n }.call(4) return?
- 8
- 16
- 4
- nil
Answer: 16. The lambda squares its argument, so calling it with 4 returns 16.
Why are lambdas often preferred over procs?
- They run faster always
- They use less memory
- Strict args and predictable return catch bugs earlier
- They can't be stored
Answer: Strict args and predictable return catch bugs earlier. Lambdas' strict argument checking and local return behaviour make them safer.
Continue this course
- Previous: Ranges
- Next: Iterators & Enumerable — Loop and transform collections with each, map, select, and reduce
- Quick reference: Ruby cheat sheet
Frequently asked questions
What's the difference between a block, a proc, and a lambda?
A block is anonymous code attached to a method call — it isn't an object you can store. A Proc wraps that idea into an actual object you can assign to a variable, pass around, and call later with .call. A lambda is a special, stricter kind of Proc created with the -> syntax. So the progression is: block (not stored) → Proc (stored, lenient) → lambda (stored, strict). All three let you treat behaviour as data.
How do lambdas and procs actually differ?
Two ways. First, argument checking: a lambda enforces the exact number of arguments and raises ArgumentError if you pass the wrong count, while a proc is lenient — extra arguments are ignored and missing ones become nil. Second, return behaviour: inside a lambda, return exits only the lambda; inside a proc, return tries to exit the enclosing method, which can surprise you. For most code, lambdas are the safer, more predictable choice.
How do I call a proc or lambda?
Use .call: square.call(4). Ruby also offers shorthands — square.(4) and square[4] both invoke it, and for lambdas you'll often see the stabby form -> (args) { body }. To turn a stored proc or lambda back into a block for a method like map or each, prefix it with &: numbers.map(&doubler). That & is the bridge between 'code as an object' and 'code as a block'.
When would I actually reach for a proc or lambda?
Whenever you want to store, pass, or choose behaviour at runtime. Common cases: a hash of operations keyed by name (a mini calculator or command dispatcher), a strategy you select based on user input, callbacks you hand to another method, or reusable transformations you apply with map. They're also the foundation of functional patterns in Ruby — composing small functions and passing them as data.