skip to content

In Ruby, what does include Singleton do to a class, and what happens when code calls new on it?

level: juniorimportance: should knowfreq 38%

answer

  1. require "singleton" first
  2. new and allocate become private
  3. Klass.instance, created on first call
  4. dup and clone raise TypeError
  5. each subclass gets its own instance

basics

~20 s

include Singleton makes new and allocate private and adds a class method instance that creates one object on first call and returns it afterwards. Calling new raises NoMethodError, and dup or clone on the instance raise TypeError.

solid answer

~40 s

After `require "singleton"`, `include Singleton` turns the class's `new` and `allocate` into private class methods, so `FeatureFlags.new` raises `NoMethodError`. It adds `FeatureFlags.instance`, which builds the object on the first call and returns the same one afterwards; creation is guarded by a `Thread::Mutex`, so two threads racing on that first call still get one object. `initialize` therefore runs once and receives no arguments. `dup` and `clone` on the instance raise `TypeError`, and `Marshal.load` of a dumped instance returns the existing `instance`. A subclass gets its own, separate instance through an `inherited` hook, and including the module in a module raises `TypeError`. The guarantee is per class and per process, and `send(:new)` can still bypass it, because private methods are callable through `send`.

code

ruby · 16 lines
ruby
require "singleton"

class FeatureFlags
  include Singleton

  def initialize
    @flags = {new_profile_page: false}
  end

  def enabled?(name) = @flags.fetch(name, false)
end

FeatureFlags.instance.equal?(FeatureFlags.instance) # => true
FeatureFlags.instance.enabled?(:new_profile_page)    # => false
FeatureFlags.new           # NoMethodError (private method 'new')
FeatureFlags.instance.dup  # TypeError

go deeper

for a junior

Recall require "singleton", include Singleton, Klass.instance for access, and that Klass.new raises NoMethodError.

for a middle

Explain lazy mutex-guarded creation, why initialize takes no arguments, the TypeError on dup and clone, and per-subclass instances.

for a senior

Point out bypasses such as send(:new) and forked processes, and handle shared state in tests given there is no reset API.

for a principal

Decide between the Singleton module, a module of functions, a constant and an injected instance for shared services, considering testability and process models.

## What the module is **`Singleton`** is a module in Ruby's standard library (`require "singleton"`, a default gem, 0.3.0 in Ruby 4.0) that makes a class have exactly one instance, reached through a class method instead of `new`. The idea of the pattern itself is general; this answer is about what Ruby's module actually changes. ## What include Singleton changes | Aspect | Effect | |---|---| | `Klass.new`, `Klass.allocate` | made private; `Klass.new` raises `NoMethodError` | | `Klass.instance` | added; creates the object on first call, then returns it | | `instance.dup`, `instance.clone` | raise `TypeError` ("can't dup instance of singleton ...") | | `Marshal.dump` / `Marshal.load` | `_dump` returns an empty String; `_load` returns `Klass.instance` | | subclasses | an `inherited` hook gives each subclass its own instance | | `Klass.clone` | the cloned class gets a fresh singleton state | | inclusion in a module | raises `TypeError` | ## Lazy, thread-safe creation The instance is **lazy**: nothing is built until the first `instance` call. That call checks a stored instance, and if none exists, takes a per-class `Thread::Mutex` and checks again before calling `new`. Two threads that call `instance` at the same moment therefore still receive the same object. Because `instance` calls `new` with no arguments, **`initialize` must take none**; configuration has to come from constants, the environment or methods called after creation. ## Ways to end up with more than one - **`FeatureFlags.send(:new)`** calls the private method anyway, producing a second, independent object. - **Subclasses** each have their own instance: `Child.instance.equal?(Base.instance)` is false. - **Processes** do not share memory: after `fork`, parent and child each hold their own copy, and changes in one are invisible to the other. - **Cloning the class** (`FeatureFlags.clone`) produces a class with its own instance. `dup`, `clone` and `Marshal` are closed off, so accidental copies of the instance are prevented; deliberate bypasses are not. ## Ruby alternatives Many Ruby codebases never include `Singleton`: 1. A **module** with `module_function` or `extend self` holds stateless helpers without any instance at all. 2. A **constant** such as `FLAGS = FeatureFlags.new.freeze` gives one shared, eagerly built object that can still be created again in tests. 3. Passing the shared object to whatever needs it keeps the dependency visible. ## Marshal and state Singleton's `_dump` returns an empty String, so `Marshal.dump(FeatureFlags.instance)` stores no instance variables, and `Marshal.load` calls `_load`, which returns `FeatureFlags.instance`. Loading never creates a second object, but it also restores none of the dumped state. A class that must round-trip its state defines its own `_dump(depth)` returning the data to keep and a class method `_load(str)` that writes it back onto `instance`. The same rule explains why a singleton cached across processes arrives "empty": only what `_dump` returned travels. A small example of the alternatives in code: - `module Slug; module_function; def call(text) = text.downcase.tr(" ", "-"); end` needs no instance at all; - `DEFAULT_FLAGS = FeatureFlags.new.freeze` works only for a class that does not include `Singleton`, and it can be rebuilt in a test. ## Testing implications The instance lives in an internal class-level instance variable and the module offers **no public reset**. State set in one test is still there in the next, so tests either avoid mutating it, reset the fields they touch, or stub `instance` to return a test double. Classes that need per-test isolation are often better served by one of the alternatives above.

  • What happens if a class that includes Singleton defines initialize(path) with a required argument?
    `instance` calls `new` with no arguments, so the first `instance` call raises `ArgumentError` for the missing argument. Singleton classes need an `initialize` without required parameters.
  • Is Singleton's instance creation safe when several threads call instance at the same time?
    Yes within one process. `instance` reads the stored object first and, when it is missing, takes a per-class `Thread::Mutex` and checks again before calling `new`, so only one object is built. That protects creation only; methods on the instance that mutate state need their own synchronization.

saying these in an interview costs you the question

  • include Singleton makes Klass.new return the shared instance
  • The Singleton instance is created when the class is loaded
  • A subclass of a Singleton class shares its parent's instance
  • Nothing can create a second instance of a Singleton class
  • Calling dup on the Singleton instance returns the same object