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

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)     # -> 100

2️⃣ 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 }) # -> 103

Your 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.5

Mini-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 here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Procs & Lambdas

ConceptRuby Syntax
ProcProc.new { |x| x }
Lambda->(x) { x }
Callf.call(3)
Call shorthandf.(3) / f[3]
To blockarr.map(&f)
Capture blockdef m(&blk)

🎉 Lesson Complete!

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

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.