December Code

Ruby modules and mixins interview questions, with answers

A Ruby class can inherit from only one class, so shared behaviour is packaged in modules and mixed in. Modules also serve as namespaces for constants and helper functions. Interviewers ask how mixing in works, where a module's methods end up, and which built-in modules turn a few methods into many.

The answers below cover modules as namespaces, include and extend, how modules differ from classes, Comparable, method lookup, module_function and the included hook, 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 module in Ruby?

    In short: A module is a container for methods and constants that cannot be instantiated, used as a namespace and as a mixin.

    A module groups related methods and constants under one name. As a namespace, it prevents name clashes: Geo::PI2 and Geo.circ below live inside Geo, and libraries wrap all their classes in one top-level module. As a mixin, its instance methods can be added to any class with include. Unlike a class, a module cannot create instances and cannot inherit or be inherited from. Methods defined with def self.name are called on the module itself, like class methods, which suits stateless helper functions.

    module Geo
      PI2 = 6.28
      def self.circ(r) = PI2 * r
    end
    p Geo.circ(2)
    p Geo::PI2
    # 12.56
    # 6.28
  2. 2.What is the difference between include and extend in Ruby?

    In short: include adds a module's methods as instance methods of a class; extend adds them to one particular object, often a class itself.

    include Hello inside a class makes Hello's methods available on every instance of that class, which is the usual way to mix behaviour in. extend adds a module's methods to a single object's singleton class. Calling extend on an ordinary object gives only that object the methods, as o.hello shows, and calling extend inside a class body, where self is the class, turns the module's methods into class methods. Rails's concerns use both, including instance methods and extending the class with class methods.

    module Hello
      def hello = "hello"
    end
    class Pal
      include Hello
    end
    o = Object.new
    o.extend(Hello)
    puts Pal.new.hello, o.hello
    # hello
    # hello
  3. 3.How is a Ruby module different from a class?

    In short: A class can be instantiated and inherited from; a module cannot do either, but it can be mixed into any number of classes.

    Classes and modules are closely related: Class is actually a subclass of Module. What a class adds is the ability to create objects with new and to take part in single inheritance. A module has neither, so calling new on one raises NoMethodError. What a module offers instead is reuse across unrelated classes: many classes can include the same module, and one class can include many modules. A common rule of thumb is to use a class for a thing and a module for a capability, such as Comparable or Enumerable.

    module M; end
    class K; end
    p M.class, K.class
    M.new
    # Module
    # Class
    # raises NoMethodError
  4. 4.How does the Comparable module work in Ruby?

    In short: Define <=> and include Comparable, and the class gains <, <=, ==, >, >=, between? and clamp built on that one method.

    Comparable is the classic example of a mixin that turns one method into many. A class defines the spaceship operator, <=>, returning a negative number, zero or a positive number when the object is less than, equal to or greater than another. Including Comparable then provides the comparison operators, between? and clamp, all implemented in terms of <=>. The same objects also sort correctly with sort and max, which call <=> directly. Enumerable works the same way for collections, built on each.

    class Ver
      include Comparable
      attr_reader :n
    
      def initialize(n)
        @n = n
      end
    
      def <=>(o) = n <=> o.n
    end
    p Ver.new(2) > Ver.new(1)
    # true
  5. 5.How does Ruby find which method to call?

    In short: It searches the receiver's ancestors in order: the class, then its included modules, then the superclass and its modules, up to BasicObject.

    Every class has an ancestors list, which is the exact order Ruby searches for a method. An included module is inserted just after the class that includes it, so the class's own methods win over the module's. When several modules are included, the one included last is searched first. The search continues through the superclass, its modules and eventually Object, Kernel and BasicObject; if nothing matches, Ruby calls method_missing. Job.ancestors below shows the start of that chain.

    module Loggable; end
    class Job
      include Loggable
    end
    p Job.ancestors.first(3)
    # [Job, Loggable, Object]
  6. 6.What does module_function do in Ruby?

    In short: It makes a module's methods callable as Module.method while also mixing them in as private instance methods where the module is included.

    Writing module_function in a module, before the methods, makes each following method available in two ways: as a module method, called as Util.twice(4), and as a private instance method in any class that includes the module. Ruby's own Math module works this way, which is why both Math.sqrt(2) and, after include Math, sqrt(2) work. The alternatives are def self.name, which defines only the module method, and extend self, which makes the public instance methods callable on the module too.

    module Util
      module_function
    
      def twice(x) = x * 2
    end
    p Util.twice(4)
    # 8
  7. 7.What is the self.included hook in a Ruby module?

    In short: Ruby calls a module's self.included(base) when it is included, letting the module also add class methods to base.

    A module's instance methods become instance methods of the including class, but modules often need class methods too, such as a macro called in the class body. Ruby calls the hook method self.included with the including class as its argument, and the common pattern is for the hook to call base.extend(ClassMethods), a nested module of class methods. Rails's ActiveSupport::Concern packages this pattern. Similar hooks exist for other events: extended, prepended and inherited.

    module Tag
      def self.included(base)
        base.extend(ClassMethods)
      end
    
      module ClassMethods
        def tag = "tagged"
      end
    end
    class Post
      include Tag
    end
    puts Post.tag
    # tagged

How the diagnostic asks it

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

Modules & Mixins · mediumRB-019

What happens when this Ruby code runs?

module Tools
  def tool
    "tool"
  end
end

class A
  extend Tools
end

puts A.tool
puts A.new.tool
  1. 1It prints tool twice
  2. 2It prints tool, then raises NoMethodErrorcorrect
  3. 3It raises NoMethodError at A.tool
  4. 4It prints tool, then nil

extend adds a module's methods to a single object's singleton class. Inside the class body, self is the class A, so extend Tools gives A itself a tool method, a class method: A.tool prints tool. Instances of A do not get it, so A.new.tool raises NoMethodError. include is the keyword that adds instance methods, so tool twice would need both. Raising at A.tool swaps the two. Calling a missing method never returns nil. A common pattern uses the included hook to extend the class with a ClassMethods module.

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