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.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.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.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.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.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.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.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.
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
- 1It prints tool twice
- 2It prints tool, then raises NoMethodErrorcorrect
- 3It raises NoMethodError at A.tool
- 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.