In Ruby, what does include Singleton do to a class, and what happens when code calls new on it?
answer
- require "singleton" first
- new and allocate become private
- Klass.instance, created on first call
- dup and clone raise TypeError
- each subclass gets its own instance
basics
~20 sinclude 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 sAfter `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 linesrequire "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 # TypeErrorgo deeper
Recall require "singleton", include Singleton, Klass.instance for access, and that Klass.new raises NoMethodError.
Explain lazy mutex-guarded creation, why initialize takes no arguments, the TypeError on dup and clone, and per-subclass instances.
Point out bypasses such as send(:new) and forked processes, and handle shared state in tests given there is no reset API.
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