skip to content

Proc vs Lambda Semantics

Procs from proc and Proc.new accept any arguments and return from the enclosing method, while lambdas check arity and return only from themselves. Interviewers probe return and LocalJumpError.

on this pageshow

explore

questions

4

In Ruby, what are the differences between a proc and a lambda, and when would you choose each?

level: middleimportance: must knowfreq 80%

answer

  1. same class, different flag
  2. arguments: strict vs lenient
  3. where return goes
  4. Proc#lambda? returns true or false
  5. ArgumentError vs nil-filled parameters

basics

~20 s

Both are Proc objects. A lambda (lambda, ->) checks arguments like a method and its return exits only the lambda; a proc (proc, Proc.new) nil-fills or drops positional arguments, and its return exits the enclosing method.

solid answer

~40 s

Procs and lambdas are both instances of `Proc`; `Proc#lambda?` tells them apart. `lambda { }` and `->(x) { }` build lambdas, which treat arguments like a method: a wrong count raises `ArgumentError`, and `return` or `break` leaves only the lambda, handing its value back to `call`. `proc { }` and `Proc.new { }` build regular procs, which behave like blocks: missing positional parameters become `nil`, extra positional arguments are dropped, a single array is splatted across several parameters, and `return` leaves the method the proc was defined in, raising `LocalJumpError` if that method has already returned. I reach for a lambda whenever a callable is stored and invoked later, such as validation rules kept in a hash, and let regular procs stay what blocks are for iterators.

code

ruby · 10 lines
ruby
strict = ->(value, field) { "#{field}: #{value}" }
loose  = proc { |value, field| "#{field}: #{value}" }

loose.call("x")    # => ": x"   (field is nil)
strict.call("x")   # ArgumentError (given 1, expected 2)

strict.lambda?     # => true
loose.lambda?      # => false
strict.class       # => Proc
loose.class        # => Proc

go deeper

for a junior

Recall the four ways to make one: proc and Proc.new give regular procs, lambda and -> give lambdas, and both are Proc objects.

for a middle

Explain both axes: lenient versus strict argument handling, including auto-splatting, and where return sends control for each kind.

for a senior

Show where the difference bites: stored callables whose return raises LocalJumpError, and why lambdas suit rules or handlers invoked long after creation.

for a principal

Frame it as an API contract: a library taking callables should state whether it relies on strict arity and build its own callables as lambdas.

## Two flavours of one class In Ruby a **proc** and a **lambda** are the same kind of object: an instance of the core class `Proc`, a chunk of code packaged with the scope it was written in so that it can be stored and called later with `call`, `.()` or `[]`. There is no separate class for lambdas. What differs is a flag fixed when the object is created, and `Proc#lambda?` reports it. | How it is created | `lambda?` | Kind | |---|---|---| | `proc { \|x\| x }` | `false` | regular proc | | `Proc.new { \|x\| x }` | `false` | regular proc | | `lambda { \|x\| x }` | `true` | lambda | | `->(x) { x }` | `true` | lambda | A block written after a method call and captured into an object is a regular proc too, which is why blocks and procs share their rules. ## Arguments: strict versus lenient A lambda treats its parameters exactly as a method does. A regular proc treats them the way a block does, generously: - **Missing arguments**: a lambda raises `ArgumentError` (`wrong number of arguments (given 1, expected 2)`); a proc fills the missing parameters with `nil`. - **Extra arguments**: a lambda raises `ArgumentError`; a proc silently discards them. - **One array, several parameters**: a proc **auto-splats** the array across its parameters, so `proc { |a, b| }.call([1, 2])` binds `a = 1, b = 2`; a lambda sees one argument and raises. - **Keywords are the exception**: a required keyword parameter is checked even in a regular proc, so `proc { |a:| a }.call` raises `ArgumentError` for the missing keyword. The leniency is deliberate. `map` or `each` over an array of pairs hands the block one packed array per element, and splatting lets the block's parameter list name its parts. ## return and break The second difference is where control goes. - In a **lambda**, `return` (and `break`) end the lambda itself. The value becomes the result of `call`, and the method that called the lambda carries on. - In a **regular proc**, `return` ends the **method the proc was defined in**, not just the proc. Code after the `call` never runs. - If that defining method has already returned, a regular proc's `return` has nowhere to go and raises `LocalJumpError` with the message `unexpected return`. The Proc documentation's own example makes the point: ```ruby def test_return -> { return 3 }.call # leaves only the lambda proc { return 4 }.call # leaves test_return return 5 end test_return # => 4 ``` ## Why Ruby keeps both Blocks passed to iterators are regular procs because iteration needs both of their quirks. Destructuring lets `{ |key, value| ... }` receive a pair, and a proc's `return` lets a method stop searching halfway through `each` and return a result from the method itself. A lambda, by contrast, behaves like a small anonymous method: it is self-contained, checks its inputs, and cannot jump out of the code that called it. The Proc reference describes lambdas as useful "as self-sufficient functions" and regular procs as useful for implementing iterators. Lambda-ness also travels with the object. Passing a lambda to a method with `&` does not turn it into a lenient block: `[[1, 2]].map(&->(a, b) { a })` raises `ArgumentError`, because the lambda still refuses one array for two parameters. ## Choosing in practice Take a form library that keeps validation rules as callables, keyed by field name, and runs them when a form is submitted: 1. **Store rules as lambdas.** A rule written `->(value) { !value.nil? }` fails loudly if the library passes the wrong number of arguments, and a `return` inside it only ends the rule. 2. **Avoid `proc { ... return ... }` for stored rules.** The rule is usually built inside a configuration method that has long since returned, so its `return` raises `LocalJumpError` the first time it runs. 3. **Use `next` to leave a regular proc early** when you do want block semantics: `next false` ends the current call and makes `false` its result without leaving any method. 4. **Let blocks stay blocks.** Code that iterates and wants to return from the surrounding method mid-loop is exactly what regular procs are for. ## Telling them apart at runtime Because both kinds share one class, `is_a?(Proc)` cannot distinguish them; `rule.lambda?` can. A library that accepts either kind can check the flag and document which semantics it relies on, but the simpler contract is to accept anything that responds to `call` and to build lambdas in its own code.

  • Why are the blocks given to each or map treated as regular procs rather than lambdas?
    Iterators need both proc quirks. Auto-splatting lets `{ |key, value| }` receive one packed pair, and a proc's `return` lets a method stop iterating and return from itself in the middle of `each`. A strict lambda would reject the single array argument and could only end itself.
  • Does passing a lambda to a method with & make it lenient like a block?
    No. Lambda-ness stays with the object for its whole life, so the method yields into strict semantics: `[[1, 2]].map(&->(a, b) { a })` raises `ArgumentError`, while the same code with `proc { |a, b| a }` returns `[1]`.
  • How do you leave a regular proc early without leaving the enclosing method?
    Use `next`, optionally with a value: `next false` ends the current invocation of the proc and makes `false` the result of `call`, and the enclosing method keeps running. `return` in the same place would leave the method instead.

saying these in an interview costs you the question

  • Lambdas are a separate class from Proc
  • return inside a proc only returns from the proc
  • A proc raises ArgumentError when given too few positional arguments
  • Proc.new creates a lambda
  • The only difference is the -> syntax
open as a page

In Ruby, which of proc, Proc.new, lambda and the -> literal create lambdas, and how do you check one?

level: juniorimportance: should knowfreq 50%

basics

~10 s

lambda { } and ->(x) { } create lambdas; proc { } and Proc.new { } create regular procs. All four return a Proc, so check with Proc#lambda?, which is true only for lambdas.

open as a page

In Ruby 4.0, how do a proc and a lambda handle missing, extra and single-array arguments when called?

level: middleimportance: should knowfreq 45%

basics

~10 s

A lambda checks arguments like a method, raising ArgumentError on any count mismatch. A regular proc fills missing positional parameters with nil, discards extra ones, and splats a single array across several parameters.

open as a page

In Ruby, why does a form-validation rule built in a method as proc { |v| return false if v.nil?; true } raise LocalJumpError later?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A regular proc's return targets the method that created it. Once that method has returned the proc is orphaned, so return raises LocalJumpError (unexpected return). Build the rule as a lambda, or use next false, to leave only the callable.

open as a page