Skip to Content
Course content

119: Common Ruby Interview Questions on Modules vs Classes

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was interviewing a developer who had a stellar resume and deep experience in Java. We got to the Ruby portion of the technical screen, and I asked them to implement a simple set of utility methods for formatting currency and dates. They immediately jumped into creating a Formatter class. When I asked why they chose a class, they said, "Well, I need to put the methods somewhere." Then, they tried to call these methods by instantiating the class: Formatter.new.format_currency(100). I could tell they were fighting the language; they were trying to force Ruby to act like a strictly object-oriented language where everything must be an instance of a class. It was a classic mistake, and it's exactly the kind of thing a picky interviewer will pounce on to see if you actually "get" Ruby or if you're just translating from another language in your head.

The "Can I New This?" Litmus Test

When an interviewer asks you the difference between a class and a module, the most fundamental answer is instantiation. You can create an object from a class using .new; you cannot do that with a module. If you try to call Module.new, Ruby will throw a NoMethodError. This sounds simple, but the "why" is where the real conversation happens. Classes are blueprints for objects that hold state. Modules, on the other hand, are designed for grouping behavior or namespacing.

If you find yourself creating a class that never holds any unique instance data—meaning you're just using it as a bucket for methods—you should probably be using a module. I've seen way too many codebases cluttered with "Helper" classes that are actually just modules in disguise. It's a smell. In Ruby, if it doesn't need to be an "object" with its own identity, it doesn't belong in a class.

Include, Extend, and the Ancestor Chain

This is where most candidates start to sweat. The interviewer will likely ask: "What's the difference between include and extend?" Here is the mental model I use: include adds the module's methods as instance methods, while extend adds them as class methods. But to really impress them, you need to talk about the ancestor chain.

module Loggable
  def log(message)
    puts "[LOG]: #{message}"
  end
end

class NetworkRequest
  include Loggable
end

# The ancestor chain: NetworkRequest -> Loggable -> Object -> Kernel -> BasicObject
puts NetworkRequest.ancestors.inspect

When you include a module, Ruby inserts that module into the inheritance hierarchy immediately above the class. This is how Ruby solves the "multiple inheritance" problem. You can't inherit from five different classes, but you can include five different modules. If you use extend, you're essentially adding those methods to the singleton class of the object, bypassing the standard instance lookup. I usually tell my juniors: use include when the object is something (like Loggable), and extend when you want to give the class itself some specialized capabilities.

Namespacing vs. Mixins

Finally, be prepared to discuss the dual nature of modules. We use them as "Mixins" (as shown above) to share behavior across unrelated classes. But we also use them for namespacing to prevent collisions in large projects. This is a distinct use case. When you wrap a class inside a module—like PaymentGateway::StripeProcessor—you aren't using the module to share methods; you're using it to organize your code.

An interviewer might ask: "Can a module have a constant?" Yes, it can. In fact, that's a primary way we handle configuration. Just remember that while a class can be both a namespace and a blueprint, a module is the "purest" way to organize your code without the overhead of object instantiation. If you can explain that distinction clearly, you've already passed the most difficult part of the Ruby architectural conversation.




📋 Practical Task

Implementing a Multi-Model Taggable System

In this exercise, you will simulate a real-world scenario where different parts of an application need "tagging" functionality, but they don't share a common base class. You need to implement a Taggable module and integrate it into two separate classes: BlogPost and Product.

Requirements:

  • Create a module named Taggable.
  • Inside Taggable, implement a method add_tag(tag) that adds a string to an array called @tags. (Assume @tags is initialized as an empty array in the class).
  • Implement a method tags in the module that returns the current list of tags.
  • Create a BlogPost class and a Product class. Both should use the Taggable module so that they can both handle tags.
  • Ensure that calling BlogPost.new.add_tag("Ruby") does not affect an instance of Product.
  • Bonus: Add a class method to the Taggable module called version that returns "1.0.0", and use extend within the classes to make that version accessible as a class method (e.g., BlogPost.version).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.