December Code

Ruby Enumerable interview questions, with answers

Enumerable is the module that gives Ruby's arrays, hashes and ranges their long list of collection methods, and idiomatic Ruby replaces most loops with them. Interviewers use Enumerable to see whether a candidate reaches for the right method and understands what each one returns.

The answers below cover each and map, filtering, reduce, grouping and sorting, making a class Enumerable, Enumerators and iterating with an index, 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 the difference between each and map in Ruby?

    In short: each runs the block for its side effects and returns the original collection; map returns a new array of the block's results.

    each is for doing something with every element, such as printing or saving, and its return value is the receiver itself, so the block's results are thrown away. map, also called collect, is for transforming: it builds a new array containing the block's value for each element and leaves the original unchanged. Using each with an empty array and push to build a result is a sign that map was the better choice. map! transforms an array in place, and flat_map maps and flattens one level.

    names = ["ana", "li"]
    up = names.map { |n| n.upcase }
    p up, names
    # ["ANA", "LI"]
    # ["ana", "li"]
  2. 2.How do select, reject and find differ in Ruby?

    In short: select keeps every element the block accepts, reject keeps those it refuses, and find returns only the first accepted element.

    select, also called filter, returns a new collection of the elements for which the block is truthy, and reject returns the rest. find, also called detect, stops at the first element for which the block is truthy and returns that element, or nil if none matches. Related predicates answer yes-or-no questions: any?, all?, none? and one?, and count with a block counts matches. partition splits a collection into two arrays, the accepted and the rejected, in one pass.

    n = (1..6).to_a
    p n.select { |x| x.even? }
    p n.reject { |x| x > 2 }
    p n.find { |x| x > 4 }
    # [2, 4, 6]
    # [1, 2]
    # 5
  3. 3.How does reduce work in Ruby?

    In short: reduce, also called inject, combines the elements into one value, passing an accumulator and each element to a block or an operator.

    reduce walks the collection with an accumulator. With a block, the block receives the accumulator and the next element and returns the new accumulator. With a symbol, such as reduce(:*), it applies that operator between elements. Without an initial value, the first element starts the accumulator; with one, reduce(init) starts from it, which matters for empty collections, where reduce without an initial value returns nil. For common cases there are dedicated methods: sum, min, max, minmax and tally.

    f = (1..5).reduce(:*)
    p f
    # 120
  4. 4.How do group_by and sort_by work in Ruby?

    In short: group_by builds a hash from each block result to the elements that produced it; sort_by orders elements by a key the block computes.

    group_by calls the block for every element and collects the elements into a hash whose keys are the block's results, so grouping numbers by x % 2 gives odd and even groups, and grouping words by length gives a hash of lengths. sort_by sorts by the value the block returns, which is simpler and usually faster than sort with a comparison block, since each key is computed once. Negating a numeric key sorts in descending order, and returning an array sorts by several keys in turn.

    n = [3, 1, 2]
    p n.group_by { |x| x % 2 }
    p n.sort_by { |x| -x }
    # {1 => [3, 1], 0 => [2]}
    # [3, 2, 1]
  5. 5.How do you make your own Ruby class Enumerable?

    In short: Include Enumerable and define an each method that yields every element; the class then gets map, select, include?, sort and the rest.

    Enumerable's methods are all written in terms of each, so a collection class that includes the module and defines each gains more than fifty methods for free. each must yield every element to the block in turn. For sort, min and max, the elements must also be comparable with <=>. This is the same mixin pattern as Comparable, and it is how custom collections such as a linked list or a paginated API result can be used exactly like arrays, including with map and include? as below.

    class Trio
      include Enumerable
    
      def each
        yield 1
        yield 2
        yield 3
      end
    end
    t = Trio.new
    p t.map { |x| x * 2 }
    p t.include?(2)
    # [2, 4, 6]
    # true
  6. 6.What is an Enumerator in Ruby?

    In short: An Enumerator is an object that can iterate a collection on demand; iteration methods called without a block return one.

    Calling each, map or other iterators without a block returns an Enumerator instead of iterating. An Enumerator supports external iteration, where the caller asks for one element at a time with next and gets StopIteration at the end, as well as chaining, as in map.with_index. Enumerators can also be built from scratch with Enumerator.new and a yielder, which suits generating sequences, and they are the basis of lazy evaluation over large or infinite sources.

    e = [10, 20].each
    p e.class
    p e.next, e.next
    # Enumerator
    # 10
    # 20
  7. 7.How do you iterate with an index in Ruby?

    In short: Use each_with_index, or chain with_index onto another iterator, as in map.with_index(1), to start the count elsewhere.

    each_with_index yields each element together with its zero-based position, which replaces a manual counter. For other iterators, with_index attaches an index to any Enumerator, so map.with_index builds an array using both the element and its position, and with_index(1) starts counting from 1, which is handy for numbered lists. each.with_index and each_with_index behave the same. Where only positions are needed, each_index or a range such as 0...array.size can be clearer.

    w = %w[a b]
    w.each_with_index do |s, i|
      puts "#{i}: #{s}"
    end
    # 0: a
    # 1: b

How the diagnostic asks it

One question from the Ruby bank, exactly as a sitting would show it. The bank has 3 on enumerable and 30 across Ruby.

Enumerable · easyRB-021

What does this Ruby code print?

r1 = [1, 2].each { |x| x * 10 }
r2 = [1, 2].map { |x| x * 10 }
p r1
p r2
  1. 1[1, 2], then [10, 20]correct
  2. 2[10, 20], then [10, 20]
  3. 3nil, then [10, 20]
  4. 4[1, 2], then [1, 2]

each runs the block for its side effects and returns the receiver, the original array, ignoring the block's values, so r1 is [1, 2]. map collects the block's value for every element into a new array, so r2 is [10, 20]. [10, 20] twice treats each like map. nil assumes each returns nothing, but it returns its receiver, which is what makes it chainable. [1, 2] twice assumes map changes nothing; only map! modifies the original. Use each for side effects and map for transformations.

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