Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
37: Common Ruby Interview Questions on Blocks and Procs
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
There are no comments for now.