Question

Ruby lambda vs Proc.new: Differences, Return Behavior, and When to Use Each

rubylambdaproc

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:

  1. Argument checking

    • A lambda behaves more like a regular method.
    • It checks the number of arguments more strictly.
    • A non-lambda Proc.new is more forgiving and may assign nil to missing parameters or ignore extra ones.
  2. return behavior

    • In a lambda, return returns from the lambda itself.
    • In a non-lambda proc, return tries to return from the surrounding method where the proc was created.

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-style Proc
  • Proc.new { ... } creates a non-lambda proc
  • proc { ... } in Ruby 1.9+ behaves like Proc.new, not like lambda

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 return can 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.

Take Quiz

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

  1. The method example starts running.
  2. A non-lambda proc is created and stored in operation.
  3. Ruby prints:
Before call
  1. operation.call runs the proc body.
  2. Inside the proc, Ruby sees:
return "stopped early"
  1. Because this is a non-lambda proc, return tries to return from the surrounding method, which is example.
  2. The method exits immediately with the value "stopped early".
  3. The line puts "After call" never runs.
  4. 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

  1. The method starts.
  2. A lambda is created.
  3. Ruby prints Before call.
  4. operation.call runs the lambda.
  5. return "lambda result" returns only from the lambda.
  6. Control goes back to the method.
  7. Ruby prints After call.
  8. 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.

Take Quiz

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 proc in Ruby 1.9+ only if you mean the same thing as Proc.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

FeaturelambdaProc.new / proc
Object typeProcProc
lambda?truefalse
Argument checkingStrict, like a methodLenient
returnReturns from itselfReturns from surrounding method
Best forMethod-like callable logicBlock-like behavior
Safer for shared logicUsually yesSometimes risky

proc vs Proc.new in Ruby 1.9+

SyntaxMeaning 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

BehaviorMethodlambdaProc.new
Has a nameYesNoNo
Strict argumentsYesYesUsually no
return exits enclosing methodMethod exits itselfNoYes, from surrounding method
Easy to pass aroundIndirectlyYesYes

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.
Take Quiz

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

  • lambda is method-like.
  • Proc.new is block-like.
  • proc in Ruby 1.9+ behaves like Proc.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.

Take Quiz

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.new example that shows lenient argument handling.
  • Print the intermediate and final results clearly.
Take Quiz

Keep learning

Related questions

View more