Question
In Ruby, especially around Ruby 1.8 and 1.9, there are subtle differences between proc, lambda, and Proc.new.
What are the differences between them?
When should you choose lambda instead of Proc.new, or vice versa?
Also, in Ruby 1.9 and later, proc and lambda behave differently. How should that be understood in practice?
Short Answer
By the end of this page, you will understand that Ruby lambda and Proc.new both create callable objects, but they behave differently in important ways. You will learn how they handle return, how strictly they check arguments, how proc changed across Ruby versions, and practical rules for choosing the right one in real code.
Concept
In Ruby, a Proc is an object that wraps a block of code so it can be stored, passed around, and called later.
That sounds simple, but Ruby has two block-like behaviors under the Proc umbrella:
- lambda-style behavior
- non-lambda proc behavior created with
Proc.new
Both are callable, but they differ in two major ways:
-
Argument checking
- A
lambdabehaves more like a regular method. - It checks the number of arguments more strictly.
- A non-lambda
Proc.newis more forgiving and may assignnilto missing parameters or ignore extra ones.
- A
-
returnbehavior- In a
lambda,returnreturns from the lambda itself. - In a non-lambda proc,
returntries to return from the surrounding method where the proc was created.
- In a
These differences matter because they affect program flow and error handling. A wrong choice can cause confusing bugs, especially when callbacks, iterators, or deferred logic are involved.
Ruby version note
Historically, Ruby 1.8 had some confusing overlap between proc, lambda, and Proc.new. In modern Ruby thinking:
lambda { ... }creates a lambda-styleProcProc.new { ... }creates a non-lambda procproc { ... }in Ruby 1.9+ behaves likeProc.new, not likelambda
So in Ruby 1.9 and later, proc is basically a shorter way to write Proc.new.
Why this matters
In real programs, callable objects are used for:
- callbacks
- configuration hooks
- event handlers
- custom sorting or filtering logic
- delayed execution
- functional-style transformations
Choosing lambda or Proc.new changes how safe and predictable that callable will be.
Mental Model
Think of both lambda and Proc.new as portable chunks of code.
But they have different personalities:
-
A lambda is like a mini method in a box.
- It expects the right inputs.
- It returns to its own caller.
- It behaves in a controlled, method-like way.
-
A non-lambda proc is like a loose block of code captured from its surroundings.
- It is more relaxed about inputs.
- It is more tied to the context where it was created.
- Its
returncan affect the outer method.
If you ask, "Which one feels more predictable and self-contained?" the answer is usually lambda.
If you ask, "Which one behaves more like a normal Ruby block passed around as an object?" the answer is usually Proc.new.
Syntax and Examples
Core syntax
my_lambda = lambda { |x| x * 2 }
my_lambda = ->(x) { x * 2 }
my_proc = Proc.new { |x| x * 2 }
my_proc = proc { |x| x * 2 }
All of these create callable objects. You can run them with .call:
my_lambda.call(5) # => 10
my_proc.call(5) # => 10
Argument behavior
Lambda checks arguments more strictly
double = ->(x) { x * 2 }
double.call(5) # => 10
double.call # ArgumentError
double.call(5, 6) # ArgumentError
Proc.new is more lenient
double = Proc.new { |x| x * 2 }
double.call(5) # => 10
double.call # nil * 2 -> error in this specific code
A better example shows the parameter handling more clearly:
show = Proc.new { |a, b| [a, b] }
show.call(1) # => [1, nil]
show.call(1, 2) # => [1, 2]
show.call(1, 2, 3) # => [1, 2]
Now compare with a lambda:
show = ->(a, b) { [a, b] }
show.call(1) # ArgumentError
show.call(1, 2) # => [1, 2]
show.call(1, 2, 3) # ArgumentError
Return behavior
Lambda returns from itself
def test_lambda
fn = -> { return "from lambda" }
result = fn.call
"method continues: #{result}"
end
test_lambda # => "method continues: from lambda"
Proc.new returns from the surrounding method
def test_proc
fn = Proc.new { return "from proc" }
result = fn.call
"method continues: #{result}"
end
test_proc # => "from proc"
The proc caused the whole method to return immediately.
Checking what kind of Proc it is
l = -> { }
p = Proc.new { }
l.lambda? # => true
p.lambda? # => false
Step by Step Execution
Consider this example:
def example
operation = Proc.new { return "stopped early" }
puts "Before call"
value = operation.call
puts "After call"
value
end
puts example
Step by step
- The method
examplestarts running. - A non-lambda proc is created and stored in
operation. - Ruby prints:
Before call
operation.callruns the proc body.- Inside the proc, Ruby sees:
return "stopped early"
- Because this is a non-lambda proc,
returntries to return from the surrounding method, which isexample. - The method exits immediately with the value
"stopped early". - The line
puts "After call"never runs. - The final output is:
Before call
stopped early
Now compare with a lambda
def example
operation = -> { return "lambda result" }
puts "Before call"
value = operation.call
puts "After call"
value
end
puts example
Step by step
- The method starts.
- A lambda is created.
- Ruby prints
Before call. operation.callruns the lambda.return "lambda result"returns only from the lambda.- Control goes back to the method.
- Ruby prints
After call. - The method returns
value, which is"lambda result".
Final output:
Before call
After call
lambda result
Real World Use Cases
When lambdas are useful
Validations and transformations
If you want a callable rule that behaves predictably like a method, use a lambda.
normalize_name = ->(name) { name.strip.downcase }
normalize_name.call(" Alice ") # => "alice"
Callbacks with clear input requirements
formatter = ->(user) { "User: #{user[:name]}" }
This is useful when the callback must receive exactly the expected arguments.
Reusable business rules
adult_check = ->(age) { age >= 18 }
This is safe because the callable has clear expectations.
When Proc.new is useful
Block-like behavior
If you want something that behaves more like a normal Ruby block, Proc.new can make sense.
printer = Proc.new { |value| puts value }
Flexible callbacks
Sometimes you do not care if callers pass too many arguments.
log = Proc.new { |message, extra| puts message }
log.call("Started", Time.now)
The extra argument is ignored.
Working with APIs that naturally use blocks
Ruby often treats blocks as a flexible language feature. Non-lambda procs can fit that style when strict method-like behavior is not needed.
Real Codebase Usage
In real Ruby projects, developers usually prefer lambdas when the callable represents a unit of logic with a defined contract.
Common lambda patterns
Configuration rules
PRICE_FORMATTER = ->(price) { format("$%.2f", price) }
Filtering and mapping helpers
active_only = ->(user) { user[:active] }
users.select(&active_only)
Guard-like logic
valid_email = ->(email) { email.include?("@") }
return unless valid_email.call(params[:email])
Because lambdas check arguments and contain their own return, they are safer in shared code.
Common Proc.new patterns
Capturing a block to store or reuse
def repeat_action(&block)
action = block
3.times { action.call }
end
The captured block is a Proc object.
Flexible internal DSLs
Ruby libraries sometimes use block-style flexibility for configuration APIs.
configurator = Proc.new do |settings|
settings[:enabled] = true
end
Practical guideline in codebases
- Use lambda for predictable callable logic.
- Use Proc.new when you explicitly want block-like semantics.
- Use
procin Ruby 1.9+ only if you mean the same thing asProc.new. - Avoid choosing based on brevity alone; choose based on behavior.
Common Mistakes
1. Assuming proc and lambda are the same
They are both Proc objects, but they do not behave the same.
p1 = proc { |x| x }
p2 = ->(x) { x }
p1.lambda? # => false
p2.lambda? # => true
2. Using return inside Proc.new without understanding the effect
Broken or surprising example:
def wrapper
fn = Proc.new { return "done" }
fn.call
"still running"
end
wrapper # => "done"
If you expected "still running", use a lambda instead.
def wrapper
fn = -> { return "done" }
fn.call
"still running"
end
wrapper # => "still running"
3. Forgetting lambda argument strictness
Broken example:
adder = ->(a, b) { a + b }
adder.call(1) # ArgumentError
Fix: pass the correct number of arguments.
adder.call(1, 2) # => 3
4. Expecting Proc.new to reject wrong argument counts
pair = Proc.new { |a, b| [a, b] }
pair.call(1) # => [1, nil]
This may hide bugs because the code does not fail immediately.
5. Choosing Proc.new when you really want a method-like callable
If the callable is part of core business logic, lambda is usually clearer and safer.
6. Confusion about Ruby versions
In Ruby 1.9+, this:
proc { |x| x }
is non-lambda behavior, like Proc.new, not like lambda.
If you need lambda semantics, write:
lambda { |x| x }
# or
->(x) { x }
Comparisons
lambda vs Proc.new
| Feature | lambda | Proc.new / proc |
|---|---|---|
| Object type | Proc | Proc |
lambda? | true | false |
| Argument checking | Strict, like a method | Lenient |
return | Returns from itself | Returns from surrounding method |
| Best for | Method-like callable logic | Block-like behavior |
| Safer for shared logic | Usually yes | Sometimes risky |
proc vs Proc.new in Ruby 1.9+
| Syntax | Meaning in Ruby 1.9+ |
|---|---|
proc { ... } | Non-lambda proc |
Proc.new { ... } | Non-lambda proc |
lambda { ... } | Lambda-style proc |
->(...) { ... } | Lambda-style proc |
Method vs lambda vs Proc.new
| Behavior | Method | lambda | Proc.new |
|---|---|---|---|
| Has a name | Yes | No | No |
| Strict arguments | Yes | Yes | Usually no |
return exits enclosing method | Method exits itself | No | Yes, from surrounding method |
| Easy to pass around | Indirectly | Yes | Yes |
Rule of thumb
- If you want a small method you can pass around, use lambda.
- If you want captured block behavior, use Proc.new.
- If you are unsure, prefer lambda for predictability.
Cheat Sheet
# Lambda
l = ->(x) { x * 2 }
l.lambda? # => true
l.call(3) # => 6
# Non-lambda proc
p = Proc.new { |x| x * 2 }
p.lambda? # => false
p.call(3) # => 6
Key rules
lambdais method-like.Proc.newis block-like.procin Ruby 1.9+ behaves likeProc.new.
Arguments
->(a, b) { [a, b] }.call(1)
# ArgumentError
Proc.new { |a, b| [a, b] }.call(1)
# => [1, nil]
Return behavior
def a
fn = -> { return "x" }
fn.call
"after"
end
# => "after"
def b
fn = Proc.new { return "x" }
fn.call
"after"
end
# => "x"
Prefer lambda when
- argument count matters
- you want predictable control flow
- you are writing reusable business logic
Prefer Proc.new when
- you want block-like semantics
- you intentionally want looser argument handling
- you are capturing and reusing a block
Quick check
callable.lambda?
FAQ
What is the main difference between lambda and Proc.new in Ruby?
The main differences are argument checking and return behavior. Lambdas are strict and method-like, while Proc.new is looser and behaves more like a regular block.
Is proc the same as Proc.new in Ruby?
In Ruby 1.9 and later, yes in practice: proc { ... } creates a non-lambda proc, like Proc.new { ... }.
Should I use lambda or Proc.new for callbacks?
Use lambda when the callback should have strict inputs and predictable control flow. Use Proc.new when you want true block-like flexibility.
Why does return inside a proc sometimes exit the outer method?
Because a non-lambda proc keeps block-like return semantics. Its return targets the surrounding method where it was defined.
How do I know whether a Proc is a lambda?
Use:
callable.lambda?
It returns true for lambdas and false for non-lambda procs.
Is the -> syntax the same as lambda?
Yes. ->(x) { x * 2 } is shorthand for creating a lambda.
Which one should beginners usually prefer?
Usually lambda, because it is more predictable and behaves more like a normal method.
Mini Project
Description
Build a small Ruby script that stores reusable pricing rules as callables. This project demonstrates when lambdas are safer than non-lambda procs by showing strict arguments and predictable return behavior in a realistic business example.
Goal
Create a script that applies a discount rule and a tax rule to a product price using lambdas, then compare one behavior with a non-lambda proc.
Requirements
- Create a lambda that applies a percentage discount to a price.
- Create a lambda that adds tax to a price.
- Call both lambdas in sequence to calculate a final price.
- Add one
Proc.newexample that shows lenient argument handling. - Print the intermediate and final results clearly.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.