-
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
179: Common Ruby Interview Questions on Blocks vs Procs vs Lambdas Revisited
Think of these three—blocks, procs, and lambdas—as different levels of commitment when you're asking someone to do a task for you.
A block is like asking a friend who is already standing next to you to hold a bag for a second. They're right there, it's a quick favor, and they aren't a separate "entity" you can hire and move to another room; they're just part of the current conversation.
A Proc is like a freelance contractor. You've signed a contract with them, and you can carry that contract (the object) around and hand it to different managers to execute whenever needed. But Procs are a bit loose; if they decide they are "done" and call a return, they don't just finish their task—they might accidentally walk out of the entire office building (the calling method) and take you with them.
A Lambda is the disciplined professional. Like the Proc, they are a portable object. But they follow the rules strictly. If a lambda returns, it just finishes its own specific task and hands control back to the manager. Plus, if you try to give them three tools when they only asked for two, they'll stop everything and throw a fit (an ArgumentError).
The Return Drama
This is the number one question in Ruby interviews. I've seen people with years of experience stumble here because the behavior feels unintuitive until you see it in action. The difference is all about scope.
def test_proc
my_proc = Proc.new { return "Proc is leaving the building!" }
my_proc.call
"This line will never be reached"
end
def test_lambda
my_lambda = -> { return "Lambda is just finishing its task" }
my_lambda.call
"This line WILL be reached!"
end
puts test_proc # Output: "Proc is leaving the building!"
puts test_lambda # Output: "This line WILL be reached!"
Notice how the Proc didn't just return a value to the call method; it returned from the test_proc method entirely. The Lambda, however, played nice. It returned from its own block and let the rest of the method finish. If you're writing a piece of logic that needs to be "pluggable" but shouldn't kill your main execution flow, always reach for a lambda.
Why Lambdas are the Strict Siblings
Beyond the return behavior, there's the "Argument Police" aspect. Blocks and Procs are very chill about how many arguments you pass them. If you pass too many, they just ignore the extras. If you pass too few, they treat the missing ones as nil.
Lambdas? Not so much. They insist on an exact match.
my_proc = Proc.new { |a, b| puts "Proc got: #{a}, #{b}" }
my_lambda = ->(a, b) { puts "Lambda got: #{a}, #{b}" }
my_proc.call(1) # Works! Prints: Proc got: 1,
my_proc.call(1, 2, 3) # Works! Prints: Proc got: 1, 2 (ignores 3)
my_lambda.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2)
my_lambda.call(1, 2, 3) # ArgumentError: wrong number of arguments (given 3, expected 2)
I personally prefer lambdas for this reason. When I'm debugging a complex system, I want the code to scream at me if I'm passing the wrong data, rather than silently failing with a bunch of nil values that cause a NoMethodError ten lines later.
When to actually use which
You'll use blocks 90% of the time (think .each or .map) because they're lightweight and don't create an object in memory. But when you find yourself needing to store a piece of logic in a variable, pass it as an argument to another method, or reuse the same logic in multiple places, that's when you move to Procs or Lambdas.
In modern Ruby, the "stabby lambda" syntax (-> { ... }) is the industry standard. You'll rarely see lambda { ... } in professional codebases anymore, but it's good to recognize it in interviews.
📋 Practical Task
Exercise: The Custom Transaction Validator
You are building a payment processing system. You need to create a TransactionProcessor class that can take different "validation rules" as arguments. Some rules should be strict, and some should be flexible.
Your Goal: Implement a system where you can pass a validation rule to a process method.
- Create a lambda called
strict_limitthat takes exactly one argument (the amount). It should returntrueif the amount is less than 100, andfalseotherwise. - Create a proc called
loose_limitthat takes one argument but doesn't crash if extra metadata is passed to it. It should returntrueif the amount is less than 500. - Write a
process(amount, rule)method that calls the rule. If the rule returnsfalse, the method should print "Transaction Rejected". If it returnstrue, it should print "Transaction Approved".
Test your code with these scenarios:
- Call
process(50, strict_limit)→ Should be Approved. - Call
process(150, strict_limit)→ Should be Rejected. - Call
process(200, loose_limit)→ Should be Approved. - Call
process(200, loose_limit, "Extra Metadata")→ This should not crash (which is why we used a Proc here).
There are no comments for now.