Ruby Blocks & Procs Cheat Sheet
Explains Ruby blocks, Procs, and lambdas, including yield, block_given?, the & operator, and their key behavioral differences.
Blocks Basics
Passing and yielding to implicit blocks.
# A block is passed implicitly to a method[1, 2, 3].each do |n| puts n * 2end# Single-line block with braces[1, 2, 3].map { |n| n * 2 } # => [2, 4, 6]# Defining a method that yields to a blockdef repeat(times) times.times { |i| yield i }endrepeat(3) { |i| puts "Iteration #{i}" }
Procs & Lambdas
Creating reusable callable objects from blocks.
# Procsquare = Proc.new { |x| x * x }square.call(4) # => 16square.(4) # => 16 (shorthand)square[4] # => 16# Lambdacube = lambda { |x| x ** 3 }cube = ->(x) { x ** 3 } # stabby lambda syntaxcube.call(3) # => 27# Converting a block to a Proc with &def run_it(&block) block.call(10)endrun_it { |x| puts x }
Blocks vs Procs vs Lambdas
Key behavioral differences to remember.
- return behavior- return inside a lambda exits just the lambda; return inside a Proc exits the enclosing method
- Arity checking- Lambdas raise ArgumentError on the wrong number of arguments; Procs silently ignore extras or fill missing ones with nil
- lambda?- Proc#lambda? returns true for lambdas and false for plain Procs, letting you inspect which one you have
- yield- Calls the block implicitly passed to the current method without naming it as a parameter
- block_given?- Returns true if the current method call included a block, used to branch behavior
- & operator- Prefixing a parameter with & converts a block to an explicit Proc, or converts a Proc/Symbol into a block when calling
Symbol#to_proc Shorthand
Compact block syntax using the & operator with symbols.
# Symbol#to_proc shorthandnames = ["alice", "bob", "carol"]names.map(&:upcase) # => ["ALICE", "BOB", "CAROL"]names.select(&:empty?) # equivalent to { |n| n.empty? }# Passing an existing proc/lambda as a blockis_even = ->(n) { n.even? }(1..10).select(&is_even) # => [2, 4, 6, 8, 10]
Closures Capturing State
Blocks and Procs close over local variables from their defining scope, letting them build stateful generators.
def make_counter count = 0 increment = -> { count += 1 } current = -> { count } [increment, current]endincrement, current = make_counterincrement.callincrement.callcurrent.call # => 2# Each call to make_counter creates a fresh closure over its own `count`other_increment, other_current = make_counterother_current.call # => 0, independent of the first counter
Currying Procs & Lambdas
Partially applying arguments to build specialized callables.
add = ->(a, b, c) { a + b + c }curried = add.curryadd5 = curried[5]add5_10 = add5[10]add5_10[20] # => 35# Useful for building pipelinesmultiply = ->(a, b) { a * b }.currydouble = multiply[2][1, 2, 3].map(&double) # => [2, 4, 6]
instance_exec for DSLs
Running a block with `self` rebound to another object, the mechanism behind config-block DSLs.
class Config attr_accessor :host, :port def self.configure(&block) config = new config.instance_exec(&block) # self inside the block becomes `config` config endendsettings = Config.configure do self.host = "localhost" self.port = 5432endsettings.port # => 5432# instance_eval works similarly but takes a block with no args re-bound;# instance_exec additionally forwards arguments into the block
Advanced Block & Proc Vocabulary
Concepts that go beyond the basic yield/block_given? toolkit.
- Enumerator- Object returned when an iterator method is called without a block (e.g. [1,2,3].each), supporting external iteration via #next
- Enumerator::Lazy- .lazy wraps a chain of map/select so elements are computed one at a time on demand instead of building intermediate arrays
- Proc#arity- Returns the number of arguments a Proc/lambda expects; negative values indicate optional or splat args
- Method#to_proc- obj.method(:name).to_proc converts a bound method into a Proc usable anywhere a block is expected, e.g. via &
- Block-local variables- do |x; y| declares y as local to the block even if a same-named variable exists outside, preventing accidental shadowing
- Binding- A Proc captures a Binding — the full lexical scope (local variables, self, method) at creation time, not just a snapshot of values
- define_method with a block- define_method(:foo) { |x| ... } defines an instance method from a block, letting it close over the surrounding scope unlike def
Enumerator & Lazy Evaluation
Building custom external iterators and chaining infinite sequences without eager allocation.
# External iteration with Enumeratorenum = [1, 2, 3].each # no block => returns an Enumeratorenum.next # => 1enum.next # => 2# Custom Enumerator from scratchfibonacci = Enumerator.new do |y| a, b = 0, 1 loop do y << a a, b = b, a + b endendfibonacci.first(10) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]# Lazy chains avoid building huge intermediate arrays(1..Float::INFINITY).lazy .select { |n| n % 3 == 0 } .map { |n| n * n } .first(5) # => [9, 36, 81, 144, 225]
Use lambda (or ->) instead of Proc.new when you need strict argument checking and a return that only exits the lambda itself — this avoids surprising early returns from the enclosing method.