AfriBa Labs Logo
AfriBa LabsProduct Studio
Ruby on Rails•
11 min read

Understanding Ruby Blocks, Procs, and Lambdas: Closures in Practice

How Ruby implements closures, execution contexts, and the critical differences between Procs and Lambdas.

AL

AfriBa Labs Engineering

Backend & Systems Engineering

Published 2026-09-28

Key Takeaways

  • Blocks are syntactic sugar for closures passed implicitly to methods and executed via yield.
  • Procs have lenient arity (ignoring extra arguments) and 'return' exits the enclosing method immediately.
  • Lambdas enforce strict arity (raising ArgumentError) and 'return' exits only the lambda itself.
  • The & operator converts between implicit blocks and explicit Proc objects in method signatures.
  • In Rails, around_action filters and custom validation DSLs rely fundamentally on closure context preservation.

1. The Implicit Block and the 'yield' Keyword

Passing behavior without object overhead

In Ruby, every method can accept an implicit block without declaring it in its parameter list. When a block is passed (using do...end or curly braces {}), the method can transfer execution to that block using the yield keyword.

Invoking yield without checking whether a caller actually provided a block results in a LocalJumpError ('no block given'). Defensive Ruby code always verifies presence using block_given? before yielding.

ruby
# Safe block invocation pattern
def with_timing(operation_name)
  return unless block_given?

  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  result = yield
  duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time

  Rails.logger.info("[BENCHMARK] #{operation_name} completed in #{duration.round(4)}s")
  result
end

# Usage:
data = with_timing("Fetch Remote Payload") do
  ExternalApiClient.fetch_latest_records
end
Safe block execution with block_given? and timing benchmark

2. The Critical Differences: Proc vs Lambda

Arity rules and the danger of unexpected method returns

Both Proc and Lambda are instances of the Proc class, but their execution semantics differ in two fundamental ways: argument enforcement (arity) and control flow (the return statement).

A Proc created with Proc.new ignores surplus arguments and fills missing ones with nil. A Lambda created with lambda {} or ->(x) { x } enforces exact arity and raises an ArgumentError if the argument count mismatches.

Even more critically, writing 'return' inside a Proc attempts to return from the scope where the Proc was originally defined. If that enclosing scope has already exited, Ruby raises a LocalJumpError. Inside a Lambda, 'return' exits only the lambda itself and control returns to the caller.

ruby
# Return inside a Proc terminates the ENCLOSING method
def test_proc_return
  p = Proc.new { return "Early exit from Proc" }
  p.call
  "This line will NEVER execute"
end

puts test_proc_return # => "Early exit from Proc"

# Return inside a Lambda exits only the lambda
def test_lambda_return
  l = -> { return "Lambda exit" }
  result = l.call
  "Enclosing method continues. Result was: #{result}"
end

puts test_lambda_return # => "Enclosing method continues. Result was: Lambda exit"
Return behavior comparison: Proc vs Lambda

3. Real-World Rails Patterns: Around Actions and Custom DSLs

Managing database transactions and tenant contexts

Rails controllers and background jobs utilize blocks to wrap requests with environmental context, such as multi-tenant switching or database read replica routing.

When implementing an around_action in Rails, the controller invokes the action via block yield, ensuring cleanup logic runs reliably even if exceptions occur during action execution.

ruby
class ApplicationController < ActionController::Base
  around_action :scope_current_tenant

  private

  def scope_current_tenant
    tenant_id = request.headers["X-Tenant-ID"] || current_user&.tenant_id
    Current.tenant = Tenant.find(tenant_id)

    # Yield control to the controller action
    yield
  ensure
    # Guarantee tenant cleanup to prevent thread leakage
    Current.reset
  end
end
Rails around_action implementing tenant isolation
Tags:#Ruby#Ruby on Rails#Closures#Architecture#Software Engineering

Related Technical Guides

Backend & APIs
7 min read•Sep 18, 2026

Why Idempotency Matters When a User Clicks 'Pay' Twice

When a client sends a payment request and experiences a network drop, retrying blindly can result in duplicate transactions. Here is how idempotency keys and atomic locking solve this permanently.

APIsDistributed SystemsIdempotency
By AfriBa Labs EngineeringRead Article