December Code

Ruby blocks, procs and lambdas interview questions, with answers

Blocks are the feature that makes Ruby look like Ruby: every loop, iterator and resource helper takes one. Procs and lambdas turn blocks into objects that can be stored and passed around. Interviews probe the differences between the three, because those differences decide what return, arguments and variables mean inside them.

The answers below cover blocks and yield, block_given?, procs, lambdas, closures, next and break, and currying, with code run on Ruby 3.4. Then take the free Ruby diagnostic — ten questions across every Ruby topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.What is a block in Ruby?

    In short: A block is a chunk of code in braces or do...end passed to a method call, which the method can run with yield.

    A block is attached to a method call, written in braces for one-liners or between do and end for longer code, and it can take parameters between pipes, |n|. It is not an object by itself; it is a special, implicit argument that every method can receive. The method runs it with yield, passing values in and receiving the block's last expression back. That is how each, map, times and File.open work: the method controls when and how often the code runs, and the caller supplies what to do.

    def greet
      yield "Ravi"
    end
    greet { |n| puts "hey #{n}" }
    # hey Ravi
  2. 2.How do you check whether a block was passed to a Ruby method?

    In short: Call block_given?, which is true when the current method received a block; yield without one raises LocalJumpError.

    A method that can work with or without a block checks block_given? before calling yield, because yielding when no block was passed raises LocalJumpError. Many built-in methods use this to change behaviour: each without a block returns an Enumerator instead of iterating. The answer here returns a default value when there is no block. A method can also name its block with an &block parameter, which is nil when no block was given, but block_given? with yield is the lighter and more common form.

    def maybe
      block_given? ? yield : "none"
    end
    puts maybe
    puts maybe { "got it" }
    # none
    # got it
  3. 3.What is a proc in Ruby?

    In short: A Proc is a block turned into an object, so it can be stored in a variable, passed around and called later with call.

    Blocks are not objects, so they cannot be kept or passed to a second method. A Proc can: proc { |x| ... } and Proc.new create one, and a method parameter written &blk converts the block it receives into a Proc. A proc runs with call, with .() or with [], and it keeps access to the variables of the place where it was created. Procs are how Ruby code stores callbacks, such as handlers kept in a hash, and & in a call turns a proc back into a block for a method such as map.

    def keep(&blk)
      blk
    end
    sq = keep { |x| x * x }
    p sq.class, sq.call(4)
    # Proc
    # 16
  4. 4.How do you create and call a lambda in Ruby?

    In short: Write lambda { |x| ... } or the stabby form ->(x) { ... }, and call it with call, .() or [].

    A lambda is a Proc object created with the lambda method or the stabby syntax, ->(x) { x * 2 }, which lets parameters be declared like a method's, with defaults and keyword arguments. It is invoked with call, with the shorthand .(), or with square brackets, and lambda? returns true for it. Because a lambda is an object, it can be stored in a variable or a hash, passed to a method, or returned from one, and & converts it into a block for methods such as map. Lambdas are the usual choice for small stored functions such as validators or formatters.

    double = ->(x) { x * 2 }
    p double.call(3)
    p double.(4), double[5]
    p double.lambda?
    # 6
    # 8
    # 10
    # true
  5. 5.How do closures work in Ruby?

    In short: Blocks, procs and lambdas capture the local variables around them by reference, so they can read and change those variables later.

    When a block, proc or lambda is created, it keeps a binding to the local variables in scope at that point. The variables themselves are shared, not copied: if the closure changes n, the change is visible outside, and it persists between calls. In the code, counter returns a lambda that keeps its own n alive after the method has returned, and each call increments it. This is how Ruby builds counters, memoised helpers and callbacks with state without defining a class. Methods defined with def, by contrast, do not see outer local variables.

    def counter
      n = 0
      -> { n += 1 }
    end
    c = counter
    c.call
    p c.call
    # 2
  6. 6.What do next and break do inside a Ruby block?

    In short: next ends the current run of the block with a value, like continue; break stops the whole method call and becomes its return value.

    Inside a block, next skips the rest of the block for the current element, and the value given to next becomes the block's result for that element, which is why map below produces 0 for the skipped element. break stops the iteration entirely: the method that took the block returns immediately, and break's value becomes that method's return value, so [1, 2, 3].each { |x| break x * 10 if x == 2 } returns 20. Both keep the method call itself in control, which is why they are preferred over exceptions for leaving a loop early.

    r = [1, 2, 3].map do |x|
      next 0 if x == 2
      x * 10
    end
    p r
    # [10, 0, 30]
  7. 7.What is currying in Ruby?

    In short: curry turns a lambda that takes several arguments into one that takes them one at a time, returning a new lambda until all are given.

    Calling curry on a lambda or proc returns a curried version: supplying fewer arguments than it needs returns a new lambda waiting for the rest, and once all arguments are supplied the original body runs. That makes it easy to build specialised functions from general ones, such as an increment from an addition, or a tax calculator with a fixed rate. Method objects obtained with method(:name) can be curried after to_proc. Currying is common in functional code and in pipelines built with the >> and << composition operators.

    add = ->(a, b) { a + b }
    inc = add.curry[1]
    p inc.(5)
    # 6

How the diagnostic asks it

One question from the Ruby bank, exactly as a sitting would show it. The bank has 4 on blocks, procs & lambdas and 30 across Ruby.

Blocks, Procs & Lambdas · easyRB-012

What does this Ruby code print?

def twice
  yield
  yield
end

count = 0
twice { count += 1 }
puts count
  1. 11
  2. 22correct
  3. 30
  4. 4It raises LocalJumpError: no block given

A method can receive a block without declaring it, and yield calls that block; twice yields two times, so the block runs twice. Blocks are closures, so count += 1 inside the block updates the count variable from the surrounding code, which ends at 2. 1 assumes the block runs once. 0 assumes the block works on a copy of count. LocalJumpError, no block given (yield), is what twice would raise if it were called without a block; here one is given. block_given? lets a method check before yielding.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 Ruby questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated