Skip to Content
Course content

37: Common Ruby Interview Questions on Blocks and Procs

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've ever sat through a Ruby interview, you know that "Blocks vs. Procs vs. Lambdas" is practically a rite of passage. Interviewers love this because it's where the "magic" of Ruby's closures meets some genuinely confusing behavior regarding how the call stack is handled. Instead of giving you a cheat sheet, let's just break things in a REPL and figure out why it's happening.

The Vanishing Return

Let's start with something simple. I'm building a tiny discount engine. I want a method that takes a price and a logic block to decide if a discount applies. I'll start with a standard block.

def apply_discount(price, &block)
  puts "Starting calculation..."
  result = block.call(price)
  puts "Calculation finished!"
  result
end

apply_discount(100) { return 80 }
# Output: 
# Starting calculation...
# => 80

Notice that "Calculation finished!" never printed. Why? Because `return` inside a block returns from the method that called the block. That's standard. But watch what happens when I move that logic into a Proc.

my_proc = Proc.new { return 80 }

apply_discount(100, &my_proc)
# Output:
# Starting calculation...
# => 80

Still the same behavior. The Proc's `return` essentially tells the `apply_discount` method, "We're done here, get out now." This is often a bug in production code, but a favorite question in interviews. Now, let's try a Lambda. I'm expecting something different here.

my_lambda = -> { 80 }

apply_discount(100, &my_lambda)
# Output:
# Starting calculation...
# Calculation finished!
# => 80

Aha! The "Calculation finished!" line actually printed. A Lambda is "polite." When it hits a `return`, it returns control back to the method that called it, rather than hijacking the entire method's return flow. In an interview, if they ask the difference, this is your first big point: Procs return from the enclosing scope; Lambdas return from themselves.

The Argument Tug-of-War

Now, let's look at how they handle arguments. I've got a function that calculates a final price based on a base price and a tax rate. I'll use a Lambda first.

tax_lambda = ->(price, tax) { price + (price * tax) }

tax_lambda.call(100, 0.05) # => 105.0
tax_lambda.call(100)       # RuntimeError: wrong number of arguments (given 1, expected 2)

Lambdas are strict. They behave like methods. If you don't give them exactly what they asked for, they throw a fit. But Procs? Procs are... relaxed. Let's see.

tax_proc = Proc.new { |price, tax| price + (price * tax) }

tax_proc.call(100, 0.05) # => 105.0
tax_proc.call(100)       # => 100.0 (Wait, what?)

I didn't get an error. I got 100.0. If you look at the math: 100 + (100 * nil). Wait, that should be a NoMethodError for multiplying by nil, right? Actually, in older Ruby versions or specific contexts, this behavior varies, but the key is that the Proc simply assigns nil to any missing arguments instead of crashing. It just tries its best to keep going.

Putting it into Perspective

So, when you're staring at a whiteboard, how do you summarize this without sounding like you're reading a manual? I think of it like this: a Block is the most lightweight, but it's not an object—you can't pass it around as a variable. A Proc is a block turned into an object, but it's "loose" with arguments and "aggressive" with returns. A Lambda is a Proc that's been taught manners; it's strict about its arguments and respects the method call stack.




📋 Practical Task

Fixing the "Leaky" Price Processor

You have a method called process_payment that logs the start and end of a transaction. However, the current implementation uses a Proc for the payment logic, which is causing the "Transaction Complete" log to be skipped entirely because the Proc is returning early from the main method.

Your Task: Rewrite the payment_logic variable to be a Lambda instead of a Proc so that the process_payment method can finish its execution and print the final log message.

def process_payment(amount, &logic)
  puts "Log: Starting transaction for $#{amount}..."
  
  # The logic is called here
  result = logic.call(amount)
  
  puts "Log: Transaction Complete."
  result
end

# CURRENT BUGGY CODE:
payment_logic = Proc.new { |amt| return amt - 5 } 

puts process_payment(100, &payment_logic)
# Expected Output:
# Log: Starting transaction for $100...
# Log: Transaction Complete.
# 95
Rating
0 0

There are no comments for now.

to be the first to leave a comment.