In Ruby, what are the differences between a proc and a lambda, and when would you choose each?
answer
- same class, different flag
- arguments: strict vs lenient
- where return goes
- Proc#lambda? returns true or false
- ArgumentError vs nil-filled parameters
basics
~20 sBoth 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 sProcs 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 linesstrict = ->(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 # => Procgo deeper
Recall the four ways to make one: proc and Proc.new give regular procs, lambda and -> give lambdas, and both are Proc objects.
Explain both axes: lenient versus strict argument handling, including auto-splatting, and where return sends control for each kind.
Show where the difference bites: stored callables whose return raises LocalJumpError, and why lambdas suit rules or handlers invoked long after creation.
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