Singleton Classes and Class-Level Behavior

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Ruby 4.0 (4.0.7, checked on 2026-10-18); features newer than Ruby 3.0 name the version that introduced them. In Ruby a class is an object, so a "class method" is nothing special: it is a method that belongs to one object (the class) rather than to every instance of it. The mechanism that makes this possible is the singleton class, a hidden class that Ruby can attach to any single object to hold the methods and mixins that only that object has. This page covers the mechanism and everything built on it: singleton methods on ordinary objects, singleton_class and class << self, define_singleton_method, the three ways to write a class method and how class methods are inherited, what changes inside class << self (constants, visibility, accessors), state kept at class level (class-level instance variables versus @@ class variables), extend, private constructors with named factories, what dup, clone, freeze and Marshal do with singleton state, and the Singleton design pattern, including why the hand-rolled @instance ||= new idiom is not thread-safe and what to use instead.

It builds on Classes and Objects and Modules and Mixins (include, prepend and extend), and on Variables, Constants and Scope for the scope rules of @, @@ and constants. The exact order in which Ruby searches the singleton class, prepended modules, the class, included modules and the superclasses is the subject of Method Lookup and super; Visibility and Encapsulation treats private and protected in full; Dynamic Dispatch and Method Definition and Hooks, eval and DSLs cover define_method, inherited, extended and instance_eval in depth; Object Lifecycle, Freezing and Copying covers dup, clone and freeze; Threads and the GVL and Ractors explain the concurrency limits that matter for shared class-level state; and SOLID and Design Patterns places the Singleton pattern among the others. For comparison, see how Java, Python (and its metaclasses), Kotlin (object and companion objects), C#, Rust (associated functions) and Swift attach behavior to a type rather than to an instance.

Every example was syntax-checked with ruby -wc, run with ruby -w on Ruby 4.0.7 and linted with RuboCop 1.92 using the configuration of the running shelf gem (double-quoted strings, NewCops: enable, Style/Documentation off). The output shown is the real output. Where an example deliberately breaks a cop to show a pitfall, or defines several classes in one file for brevity, an inline rubocop:disable comment says so. The examples use the shelf gem (a small library catalogue, see the shelf gem source) and are run with ruby -Ilib, so require "shelf" finds it.

The idea in one page

Every Ruby object has a class, and a method call is looked up in that class and its ancestors. Sometimes one object needs behavior that no other instance of its class should have. Ruby does not copy the class for it; it lazily creates an anonymous class that is inserted in front of the object’s real class in the lookup path. That class is the singleton class (also called the eigenclass or, for classes, the metaclass; the official documentation says "singleton class").

Concept Meaning

Singleton method

A method defined on one object only: def obj.name, def self.name inside a class body, or define_singleton_method.

Singleton class

The hidden class that stores an object’s singleton methods and the modules added with extend. Reached with singleton_class or class << obj.

Class method

A singleton method of a Class object. def self.build in class Reader defines it in #<Class:Reader>, the singleton class of Reader.

extend

Includes a module into an object’s singleton class, so the module’s methods become singleton methods of that object (class methods when the object is a class).

Class-level instance variable

An @name set while self is a class. It belongs to that one class object and is not shared with subclasses.

Class variable

A @@name shared by a class, its subclasses and its instances (and, indirectly, by its singleton-class bodies). Widely considered a trap.

Singleton pattern

A design that allows one instance of a class. Unrelated to singleton classes by mechanism, although both use the word. In Ruby it is implemented with private_class_method :new or the Singleton module.

The two meanings of "singleton" are worth keeping apart from the start. A singleton class is a language mechanism that every object may have; the Singleton pattern is a design choice about how many instances a class may have. Class methods use the first; the Singleton module and @instance ||= new implement the second, using class methods.

The lookup path for an object that has singleton behavior, and for a class (which is also an object that has a singleton class):

flowchart TD subgraph OBJ["Method call on an ordinary object"] direction TB o1["object: catalog"] --> o2["singleton class of catalog (define_singleton_method, extend)"] o2 --> o3["class: Shelf::Catalog"] o3 --> o4["included modules: Enumerable"] o4 --> o5["superclass chain: Object, Kernel, BasicObject"] end subgraph CLS["Method call on a class (a class method)"] direction TB c1["object: Librarian"] --> c2["singleton class of Librarian (def self.x)"] c2 --> c3["singleton class of Reader (superclass)"] c3 --> c4["singleton class of Object, then of BasicObject"] c4 --> c5["Class, Module, Object, Kernel, BasicObject"] end

Singleton methods on one object

A method defined with the object as the receiver after def is a singleton method. The receiver can be a local variable, a constant, self, or any expression in parentheses (def (expression).name). A literal such as def (1).twice is a syntax error: "cannot define singleton method for literals".

# frozen_string_literal: true

require "shelf"

catalog = Shelf::Catalog.new
other = Shelf::Catalog.new

# A singleton method belongs to this one object only.
def catalog.size = count

catalog.add(Shelf::Book.new(Shelf::Isbn.new("978-0-306-40615-7"), "Ruby", Shelf::Author.new("Matz")))

puts catalog.size
p catalog.singleton_methods
p other.singleton_methods
p other.respond_to?(:size)

begin
  other.size
rescue NoMethodError => e
  puts e.message
end
1
[:size]
[]
false
undefined method 'size' for an instance of Shelf::Catalog

singleton_methods lists the public and protected methods in the object’s singleton class and, by default, in the modules the object was extended with (singleton_methods(false) restricts it to the singleton class itself). The second catalog did not get the method: the receiver of def was catalog, not Shelf::Catalog. A def obj.name evaluates obj once, at the moment of the definition; if the variable is later reassigned, the method stays with the original object.

Inside any method body, def self.name defines a singleton method on whatever self is at that moment, which is the instance, not the class. That works, but it is almost always a mistake: each call creates a new singleton method (and a singleton class) on that object.

Singleton methods are cheap to write and expensive to reason about. They are visible in methods, in singleton_methods and in method(:name).owner, but not in the object’s class, so reading the class definition does not tell you what the object can do. Prefer a module, a decorator or a subclass for behavior that more than one place needs; reserve per-object methods for test doubles, stubs, DSL receivers and one-off configuration.

The singleton class

singleton_class returns the object’s singleton class, creating it if needed. class << obj ... end opens it for definitions, and class << obj; self; end is the pre-singleton_class way of getting it. Every object has at most one, and singleton_class returns the same object each time.

# frozen_string_literal: true

require "shelf"

catalog = Shelf::Catalog.new

sc = catalog.singleton_class
puts sc.inspect.sub(/0x\h+/, "0x...")
p sc.singleton_class?
p Shelf::Catalog.singleton_class?
p sc.attached_object.equal?(catalog)
p sc.superclass
p sc.instance_methods(false)

catalog.define_singleton_method(:titled) { |prefix = "#"| "#{prefix}#{count}" }
p catalog.titled
p catalog.titled("@")
p sc.instance_methods(false)
p catalog.singleton_method(:titled).arity
p catalog.method(:titled).owner == sc

begin
  Shelf::Catalog.attached_object
rescue TypeError => e
  puts e.message
end
#<Class:#<Shelf::Catalog:0x...>>
true
false
true
Shelf::Catalog
[]
"#0"
"@0"
[:titled]
-1
true
'Shelf::Catalog' is not a singleton class

Things to notice in the output:

  • Its inspect is #<Class:#<Shelf::Catalog:0x...>>; for a class it is #<Class:Shelf::Catalog>. The address is scrubbed in this example because it changes on every run.

  • singleton_class? (Module#singleton_class?) is true for a singleton class and false for a normal class.

  • attached_object (Ruby 3.2, Class#attached_object) returns the one object the singleton class belongs to, and raises TypeError ("is not a singleton class") for any other class.

  • The superclass of an object’s singleton class is the object’s class, which is exactly the position in the lookup path that the diagram above shows.

  • define_singleton_method(name, callable = nil, &block) is the metaprogramming form of def obj.name. It accepts a block, a Proc, a Method or an UnboundMethod, returns the name as a symbol, and creates the method with a block’s self bound to the receiver and with the block’s closure intact. singleton_method(:name) fetches it as a Method object (raising NameError if the object has no such singleton method).

  • A singleton class cannot be instantiated ("can’t create instance of singleton class") or subclassed ("can’t make subclass of singleton class").

Which objects can have a singleton class

Every object that is a regular heap object can: strings, arrays, hashes, ordinary instances, classes and modules. A few cannot, because they have no identity of their own to attach a class to, or because they are shared:

# frozen_string_literal: true

[1, :sym, 1.5, nil, true, "literal", String.new("built"), [1]].each do |value|
  label = value.inspect[0, 12].ljust(12)
  shown = value.singleton_class.inspect.sub(/0x\h+/, "0x...")
  puts "#{label} #{shown}"
rescue TypeError => e
  puts "#{label} #{e.class}: #{e.message}"
end

def nil.blank? = true
p nil.blank?
p NilClass.instance_method(:blank?).owner
p 1.respond_to?(:blank?)

number = 42
begin
  def number.double = self * 2
rescue TypeError => e
  puts "#{e.class}: #{e.message}"
end
1            TypeError: can't define singleton
:sym         TypeError: can't define singleton
1.5          TypeError: can't define singleton
nil          NilClass
true         TrueClass
"literal"    TypeError: can't define singleton
"built"      #<Class:#<String:0x...>>
[1]          #<Class:#<Array:0x...>>
true
NilClass
false
TypeError: can't define singleton
Object Result of singleton_class

Integer, Float and Symbol values

TypeError: "can’t define singleton". These values are immediate or shared, so there is no per-object place for methods.

nil, true, false

Their own class, NilClass, TrueClass or FalseClass, because each of them is the only instance. def nil.blank? therefore adds a method to every nil, and only to nil.

A string literal under # frozen_string_literal: true

TypeError: the literal is a deduplicated, frozen object shared by every use of the same text. String.new("built") or "literal".dup give a fresh object that can have one.

Any other object

A fresh singleton class, created on first request.

Frozen objects can have a singleton class but cannot gain methods: defining a singleton method on a frozen object raises FrozenError, as the section on copying and freezing below shows. Instances of Data (Shelf::Book, Shelf::Isbn) are frozen from construction, so def book.shelf_mark fails with FrozenError; that is the reason the examples on this page use Shelf::Catalog and Shelf::Loan as the receivers of singleton methods.

Class methods are singleton methods

A class is an instance of Class, so a class method is a singleton method of that object, stored in the class’s singleton class. Ruby offers three spellings that produce the same method:

# frozen_string_literal: true

# Three spellings of the same thing: a method on the class object.
class Reader
  def self.from_card(number) = new("card-#{number}")

  def Reader.from_name(name) = new(name.downcase) # rubocop:disable Style/ClassMethods

  class << self
    def anonymous = new("guest")

    private

    def secret_name = "hidden"
  end

  attr_reader :name

  def initialize(name)
    @name = name
  end
end

p Reader.from_card(7).name
p Reader.from_name("ADA").name
p Reader.anonymous.name
p Reader.singleton_methods.sort
p Reader.singleton_class.private_instance_methods(false)
p Reader.respond_to?(:secret_name)
p Reader.new("x").respond_to?(:from_card)

begin
  Reader.secret_name
rescue NoMethodError => e
  puts e.message
end
"card-7"
"ada"
"guest"
[:anonymous, :from_card, :from_name]
[:secret_name]
false
false
private method 'secret_name' called for class Reader
Spelling Use it when Notes

def self.name

One or a few class methods; the default.

self is the class being defined, so renaming the class does not break it. Unaffected by a preceding private (see below).

def ClassName.name

Almost never.

Repeats the class name, breaks on rename, and RuboCop’s Style/ClassMethods flags it.

class << self …​ end

Several class methods, private class methods, class-level attr_accessor, aliases, or constants for those methods.

Opens the singleton class; private, protected, alias_method, attr_* and include apply to it. RuboCop’s Style/ClassMethodsDefinitions (disabled by default) can enforce one of the two styles (def_self or self_class).

singleton_methods reports the three class methods (it sorted them for stable output); Reader.respond_to?(:secret_name) is false because that method is private in the singleton class; instances do not respond to class methods because they are in a different class. A private class method raises NoMethodError ("private method 'secret_name' called for class Reader", with straight quotes since Ruby 3.4) when called with an explicit receiver, exactly like a private instance method.

How class methods are inherited

A subclass inherits its superclass’s class methods because the singleton class of a subclass is a subclass of the singleton class of the superclass. The chain runs parallel to the normal class chain, and ends by joining it: the singleton class of BasicObject has Class as its superclass.

# frozen_string_literal: true

# rubocop:disable Style/OneClassPerFile
class Reader
  def self.kind = "reader"
end

class Librarian < Reader
  def self.kind = "#{super} with keys"
end

p Librarian.kind
p Librarian.singleton_class.superclass
p Librarian.singleton_class.superclass.equal?(Reader.singleton_class)
p Librarian.singleton_class.ancestors.first(4)
p Reader.singleton_class.superclass
p BasicObject.singleton_class.superclass
p Class.singleton_class.ancestors.first(3)
p Reader.singleton_class.singleton_class
p Reader.singleton_class.singleton_class.singleton_class
p Librarian.method(:kind).owner
p Librarian.method(:kind).super_method.owner
# rubocop:enable Style/OneClassPerFile
"reader with keys"
#<Class:Reader>
true
[#<Class:Librarian>, #<Class:Reader>, #<Class:Object>, #<Class:BasicObject>]
#<Class:Object>
Class
[#<Class:Class>, #<Class:Module>, #<Class:Object>]
#<Class:#<Class:Reader>>
#<Class:#<Class:#<Class:Reader>>>
#<Class:Librarian>
#<Class:Reader>

That chain is the reason three common facts hold:

  • Librarian.kind finds Reader.kind and super inside a class method calls the parent’s class method.

  • Reader.new works for every class: new is an instance method of Class, and Class is where the class-method chain ends (Reader.method(:new).owner is Class).

  • Reader.name, Reader.instance_methods, Reader.include, Reader.attr_accessor and the rest of the methods of Module are reachable for the same reason; attr_accessor and private are not keywords, they are ordinary class methods called with an implicit self.

Singleton classes of singleton classes exist too (#<Class:#<Class:Reader>>), created lazily; the line printing them shows that the pattern repeats without end. In practice you never need to go further than one level.

What changes inside the singleton class body

Inside class << self the current class (self and the default definition target) is the singleton class. That has four consequences worth knowing.

# frozen_string_literal: true

class Branch
  LIMIT = 3

  class << self
    attr_accessor :default_hours

    def describe = "#{name}: limit #{LIMIT}, hours #{default_hours.inspect}"

    # Defined in the singleton class, not in Branch.
    DEFAULT = "main"

    def default = DEFAULT
  end
end

Branch.default_hours = "9-17"
puts Branch.describe
p Branch.default
p Branch.constants
p Branch.singleton_class.constants.sort
p Branch.const_defined?(:DEFAULT, false)

begin
  Branch::DEFAULT
rescue NameError => e
  puts e.message
end
puts Branch.singleton_class.const_get(:DEFAULT)
p Branch.new.respond_to?(:default_hours)
Branch: limit 3, hours "9-17"
"main"
[:LIMIT]
[:DEFAULT]
false
uninitialized constant Branch::DEFAULT
main
false
  • attr_accessor creates class-level accessors (here Branch.default_hours), backed by class-level instance variables, not instance accessors.

  • A constant assigned inside class << self belongs to the singleton class, not to Branch. Branch.constants does not list DEFAULT, Branch::DEFAULT raises NameError, and only the singleton class can resolve it. Constants that belong to the class go outside the block, where LIMIT lives; LIMIT is still visible inside the block because constant lookup also follows the lexical scope outward.

  • private, protected and public inside the block apply to the following class-method definitions, giving private class methods (secret_name in the previous example).

  • The block returns the value of its last expression, which is why class << obj; self; end returns the singleton class.

Visibility of class methods

A bare private changes the default visibility of the instance methods defined after it. It has no effect on def self.x, because that method goes to the singleton class, whose default visibility is separate. This is a very common mistake, and RuboCop’s Lint/IneffectiveAccessModifier exists for it. There are three correct ways to make a class method private:

# frozen_string_literal: true

class Vault
  private

  # "private" does not reach def self.x (deliberate offense).
  def self.open_door = "still public" # rubocop:disable Lint/IneffectiveAccessModifier

  def hidden_instance = "really private"

  class << self
    private

    def real_secret = "private on the singleton class"
  end

  def self.helper = real_secret

  private_class_method :helper
end

p Vault.open_door
p Vault.respond_to?(:real_secret)
p Vault.respond_to?(:helper)
p Vault.private_methods(false).sort
p Vault.new.respond_to?(:hidden_instance)
p Vault.send(:helper)
"still public"
false
false
[:helper, :inherited, :initialize, :real_secret]
false
"private on the singleton class"
  • private inside class << self, as with real_secret.

  • private_class_method :name after the definition, as with helper. Since the definition expression returns the method name, private_class_method def self.helper ... end works too. public_class_method is its inverse.

  • The wrong way is private def self.name, which fails with NameError ("undefined method 'name' for class 'Vault'") because private looks the name up among instance methods.

Vault.send(:helper) still calls it: visibility in Ruby is advisory, see Visibility and Encapsulation.

State kept at class level

Two kinds of variable can hold data on a class, and they behave very differently.

Class-level instance variable (@name) Class variable (@@name)

Where it lives

On the class object, like any instance variable of any object.

In the class where it is first assigned; shared by that class and every subclass.

Inheritance

Not inherited: each subclass starts with none (nil).

Shared: a subclass reads and writes the same variable.

Access

@name in a class method or in class << self; from instances only through a reader on the class.

@@name from class methods, instance methods and the class body.

Reflection

instance_variables, instance_variable_get, instance_variable_set

class_variables, class_variable_get, class_variable_set, class_variable_defined?, remove_class_variable

Style guides

Preferred.

Avoided; RuboCop’s Style/ClassVars flags every use.

# frozen_string_literal: true

# rubocop:disable Style/OneClassPerFile, Style/ClassVars
class Counter
  @instances = 0 # class-level instance variable: belongs to Counter itself
  @@created = 0  # class variable: shared with every subclass

  class << self
    attr_reader :instances

    def track
      @instances = (@instances || 0) + 1
      @@created += 1
    end

    def created = @@created
  end

  def initialize
    self.class.track
  end
end

class SpecialCounter < Counter
end

Counter.new
SpecialCounter.new
SpecialCounter.new

p Counter.instances
p SpecialCounter.instances
p Counter.created
p SpecialCounter.created
p Counter.instance_variables
p SpecialCounter.instance_variables
p Counter.class_variables
p SpecialCounter.class_variables
p Counter.new.instance_variables
# rubocop:enable Style/OneClassPerFile, Style/ClassVars
1
2
3
3
[:@instances]
[:@instances]
[:@@created]
[:@@created]
[]

Counter.instances and SpecialCounter.instances differ because @instances is a separate variable on each class object (and the reader attr_reader :instances sits in the singleton class of Counter, which SpecialCounter inherits). @@created is one variable seen by both, and instances of Counter themselves have no instance variables from either kind (Counter.new.instance_variables is empty). Note that @@created is read and written inside class << self: class variable lookup uses the lexically enclosing class and skips singleton classes, so those methods still reach the class’s variable.

Why class variables are a trap

# frozen_string_literal: true

# rubocop:disable Style/OneClassPerFile, Style/ClassVars
class Parent
  @@shared = "parent"
  @value = "parent"

  class << self
    attr_accessor :value

    def shared = @@shared
  end
end

class Child < Parent
  @@shared = "child" # reassigns the variable Parent sees
end
# rubocop:enable Style/OneClassPerFile, Style/ClassVars

p Parent.shared
p Child.value
Child.value = "set on child"
p Parent.value
p Child.value

begin
  Object.class_variable_get(:@@missing)
rescue NameError => e
  puts e.message
end
"child"
nil
"parent"
"set on child"
uninitialized class variable @@missing in Object
  • Silent sharing. Child assigned @@shared, and Parent.shared now returns the child’s value: there is one variable. Any subclass, including one in a plugin, can change what the parent and every sibling see. The class-level instance variable @value has no such leak: Child.value = ... leaves Parent.value alone.

  • Ruby 3.0 errors. Reading or writing a class variable at the top level (outside any class) raises RuntimeError ("class variable access from toplevel"). Defining the same class variable in a subclass first and in the parent later raises RuntimeError on access, because the subclass’s variable has been "overtaken":

# frozen_string_literal: true

# rubocop:disable Style/OneClassPerFile, Style/ClassVars
class Parent
  LEVEL = 0
end

class Child < Parent
  @@level = "child"
end

class Parent
  @@level = "parent" # defined later in the superclass: the subclass is "overtaken"
end

begin
  Child.class_variable_get(:@@level)
rescue RuntimeError => e
  puts "#{e.class}: #{e.message}"
end
# rubocop:enable Style/OneClassPerFile, Style/ClassVars
RuntimeError: class variable @@level of Child is overtaken by Parent

Source: Ruby 3.0.0 NEWS

  • Thread safety. @@count += 1 is a read followed by a write. Concurrent threads can lose updates, and so can any class-level state; guard shared mutable class-level data with a Thread::Mutex (see the lazy singleton below) or avoid it.

  • Ractors. A non-main Ractor can read a class-level instance variable only if its value is shareable ("can not get unshareable values from instance variables of classes/modules from non-main Ractors"), cannot assign one ("can not set instance variables of classes/modules by non-main Ractors"), and cannot touch class variables at all ("can not access class variables from non-main Ractors"); each is a Ractor::IsolationError. See Ractors.

The usual guidance is therefore: use class-level instance variables (with a class << self reader) for state that belongs to one class, use a constant for state that does not change, and treat @@ as legacy.

Giving each subclass its own state

Because class-level instance variables are not inherited, a base class that wants every subclass to start with its own collection initializes it in the inherited hook, which Ruby calls on the superclass with each new subclass.

# frozen_string_literal: true

# rubocop:disable Style/OneClassPerFile
class Registry
  @entries = []

  class << self
    attr_reader :entries

    def inherited(subclass)
      super
      subclass.instance_variable_set(:@entries, [])
    end

    def register(name) = entries << name
  end
end

class Books < Registry; end
class Authors < Registry; end

Registry.register(:base)
Books.register(:isbn)
Authors.register(:name)

p Registry.entries
p Books.entries
p Authors.entries
p Registry.subclasses.sort_by(&:name)
# rubocop:enable Style/OneClassPerFile
[:base]
[:isbn]
[:name]
[Authors, Books]

Source: Class#inherited

inherited must call super so that hooks higher in the chain still run, and runs before the subclass body is evaluated, so Books already has its own @entries inside its class body. Class#subclasses (Ruby 3.1) lists the direct subclasses (Registry.subclasses); it does not include the class itself or indirect descendants, and the order is not specified, which is why the example sorts it. Lazy initialization in the reader (@entries ||= []) is an alternative to the hook when the subclass never needs the value before first use.

Extending objects and classes with modules

extend(*modules) is singleton_class.include(*modules) plus the extended hook. The module’s instance methods become singleton methods of the receiver: on an ordinary object they are per-object behavior, and on a class they are class methods. When a second module is extended later, it is searched before the first.

# frozen_string_literal: true

require "shelf"

# rubocop:disable Style/OneClassPerFile
module Greeting
  def greet = "Welcome to #{self.class}"
end

module Finders
  def find_by_title(catalog, title) = catalog.find { |book| book.title == title }
end

class Archive < Shelf::Catalog
  extend Finders # class methods: Finders goes into Archive's singleton class
end
# rubocop:enable Style/OneClassPerFile

archive = Archive.new
archive.extend(Greeting) # instance-level: Greeting goes into this one object's singleton class
puts archive.greet
p archive.singleton_class.include?(Greeting)
p(archive.singleton_class.ancestors.first(3).map { |mod| mod.inspect.sub(/0x\h+/, "0x...") })
p Archive.new.respond_to?(:greet)
p archive.is_a?(Greeting)

book = Shelf::Book.new(Shelf::Isbn.new("978-0-306-40615-7"), "Ruby", Shelf::Author.new("Matz"))
archive.add(book)
p Archive.find_by_title(archive, "Ruby") == book
p Archive.singleton_class.include?(Finders)
p Archive.include?(Finders)
p Archive.singleton_class.ancestors.first(4)

# extend is the same as including into the singleton class
class << archive
  include Finders
end
p archive.find_by_title(archive, "Ruby").title
Welcome to Archive
true
["#<Class:#<Archive:0x...>>", "Greeting", "Archive"]
false
true
true
true
false
[#<Class:Archive>, Finders, #<Class:Shelf::Catalog>, #<Class:Object>]
"Ruby"

Source: Object#extend

  • archive.singleton_class.ancestors lists the singleton class, then Greeting, then the class, which is where extend places a module. archive.is_a?(Greeting) is true after extend because is_a? consults the singleton class too; other Archive instances are unaffected.

  • extend on a class (extend Finders in Archive) is the idiom for class methods that live in a reusable module, such as the def_delegator of Forwardable, which is made available with extend Forwardable. The self.included(base) plus ClassMethods idiom built on top of it is covered in Modules and Mixins, together with the extended and prepended hooks.

  • Archive.include?(Finders) is false: extend and include target different classes, which is why an extended module’s methods are not instance methods.

  • Module#extend_object is the hook that does the work and can be overridden (the Singleton module undefines it, so extending an object with Singleton fails).

module_function and extend self create class-level (module-level) copies of a module’s methods and are the usual way to write a module of utility functions; see Modules and Mixins.

Defining singleton methods programmatically

define_singleton_method is the right tool when the name or the body is computed. instance_eval and class_eval are frequently confused with each other because both take a block and both evaluate def, yet they define in different places:

# frozen_string_literal: true

require "shelf"

klass = Class.new
klass.instance_eval do
  # a singleton method of klass
  def made_by_instance_eval = :class_method
end
klass.class_eval do
  # an instance method of klass
  def made_by_class_eval = :instance_method
end

p klass.singleton_methods
p klass.instance_methods(false)
p klass.made_by_instance_eval
p klass.new.made_by_class_eval

obj = Object.new
obj.instance_eval { def only_here = :singleton }
p obj.singleton_methods
p obj.singleton_class.instance_methods(false)
p Object.new.respond_to?(:only_here)

# class << self opens the singleton class; self there is that class
inside = Object.new
captured = class << inside; self; end
p captured == inside.singleton_class
[:made_by_instance_eval]
[:made_by_class_eval]
:class_method
:instance_method
[:only_here]
[:only_here]
false
true
  • instance_eval changes self to the receiver and the default definition target to the receiver’s singleton class. On a class that defines a class method; on an ordinary object it defines a singleton method.

  • class_eval (Module#class_eval, docs) changes self to the module and the default definition target to the module itself, so def defines instance methods.

  • class << obj; self; end returns the singleton class, and the singleton class’s own instance_methods(false) are the methods you see in singleton_methods.

The singleton_method_added hook (a private method of BasicObject, called on the object whenever a singleton method is added) and its relatives method_added, const_added and singleton_method_removed are the introspection side of the same mechanism; define_singleton_method triggers it just as def does.

# frozen_string_literal: true

require "shelf"

# Generated class-level factories, one per loan state, plus a hook
# that records every singleton method defined after it.
class LoanFactory
  class << self
    attr_reader :added

    def singleton_method_added(name)
      super
      (@added ||= []) << name
    end
  end

  Shelf::Loan::STATES.each do |state|
    define_singleton_method(:"#{state}_for") do |book|
      Shelf::Loan.new(book, state:)
    end
  end

  def self.manual = :manual
end

book = Shelf::Book.new(Shelf::Isbn.new("978-0-306-40615-7"), "Ruby", Shelf::Author.new("Matz"))
puts LoanFactory.lent_for(book).describe
puts LoanFactory.returned_for(book).describe
p LoanFactory.singleton_methods(false).sort
p LoanFactory.added
Ruby is lent
Ruby came back
[:added, :available_for, :lent_for, :manual, :overdue_for, :returned_for, :singleton_method_added]
[:singleton_method_added, :available_for, :lent_for, :overdue_for, :returned_for, :manual]

The hook is defined first because it only sees methods added after its own definition, and it also reports itself and the attr_reader. Production code rarely needs it; it is how libraries notice class-level definitions made by their users (see Hooks, eval and DSLs).

Factories and a private constructor

Class methods are the place for alternative constructors. Ruby’s new is itself a class method (an instance method of Class) that allocates an object and calls initialize. A class that wants every instance created through a named factory makes new private in its singleton class and exposes factory methods, which can call new with an implicit receiver:

# frozen_string_literal: true

require "shelf"

# A class whose only construction path is a named factory.
class Receipt
  attr_reader :loan, :due_in

  class << self
    def for(loan, days: Shelf.config.loan_days) = new(loan, days)

    def count = @count ||= 0

    def record = @count = count + 1

    private :new
  end

  def initialize(loan, due_in)
    @loan = loan
    @due_in = due_in
    self.class.record
  end
end

book = Shelf::Book.new(Shelf::Isbn.new("978-0-306-40615-7"), "Ruby", Shelf::Author.new("Matz"))
receipt = Receipt.for(Shelf::Loan.new(book, state: :lent))
p receipt.due_in
p Receipt.count

begin
  Receipt.new(nil, 1)
rescue NoMethodError => e
  puts e.message
end

p Receipt.allocate.due_in
p Receipt.singleton_class.private_instance_methods(false)
14
1
private method 'new' called for class Receipt
nil
[:new]

Source: Module#private

The pattern is the same one the Singleton pattern uses below. Three points:

  • A named factory (Receipt.for(loan)) states the intent, can supply defaults from configuration, and can return a cached or a differently-typed object; Date.today and Time.now are standard-library examples.

  • private :new inside class << self hides the constructor for outsiders, but not from allocate: Receipt.allocate returns an uninitialized instance (all instance variables are nil, as due_in shows) and send(:new) works. Privacy of new prevents accidents, not determined callers. Singleton also makes allocate private.

  • Keep initialize free of I/O and heavy work. Once a factory exists, it is the natural place to open the file or read the configuration, and to pass the result to new.

The same section of code shows that per-class bookkeeping (Receipt.count) is an ordinary class-level instance variable memoized with ||= on first use.

Copying, freezing and Marshal

Singleton state interacts with the copying methods in a way that surprises people. dup makes a copy without singleton methods or extended modules; clone copies them and the frozen state, unless freeze: false/freeze: true overrides it. A frozen object cannot get new singleton methods. Marshal.dump refuses any object that has singleton methods.

# frozen_string_literal: true

require "shelf"

book = Shelf::Book.new(Shelf::Isbn.new("978-0-306-40615-7"), "Ruby", Shelf::Author.new("Matz"))
loan = Shelf::Loan.new(book)
loan.define_singleton_method(:label) { "main branch" }

p loan.dup.respond_to?(:label)
p loan.clone.respond_to?(:label)

loan.freeze
p loan.clone.frozen?
p loan.clone(freeze: false).frozen?
p loan.dup.frozen?

begin
  loan.define_singleton_method(:other) { 1 }
rescue FrozenError => e
  puts e.message.partition(": ").first
end

begin
  Marshal.dump(loan.clone(freeze: false))
rescue TypeError => e
  puts "#{e.class}: #{e.message}"
end

plain = Shelf::Loan.new(book)
p Marshal.load(Marshal.dump(plain)).state
false
true
true
false
false
can't modify frozen Shelf::Loan
TypeError: singleton can't be dumped
:available

Source: Kernel#clone

  • loan.dup lost label; loan.clone kept it. Use clone when the singleton behavior is part of the object’s identity (test doubles, extended objects) and dup when you want a plain copy of the data.

  • After freeze, clone is frozen and clone(freeze: false) is not; dup is never frozen.

  • FrozenError is raised when defining a singleton method on a frozen object, as it is for any other mutation.

  • Marshal.dump raises TypeError ("singleton can’t be dumped") for an object that has singleton methods. A plain object round-trips normally. The Singleton module, below, adds a _dump/_load pair that makes Marshal return the one instance instead.

The Singleton pattern

The Singleton pattern guarantees a single instance of a class and a global way to reach it. It is the most overused of the classic patterns: a global with a constructor. It is legitimate for genuinely process-wide resources (a configuration registry, a connection to one device), and a poor default for anything that code or tests want to replace. Ruby offers several ways to get one-instance behavior. First, the version found in many tutorials and books:

The hand-rolled version and its race

# frozen_string_literal: true

class Registry
  def self.instance
    @instance ||= new
  end

  private_class_method :new
end

Source: Class#new

This reads well and is wrong under threads. @instance ||= new expands to a test of @instance for nil or false, followed by an assignment of the result of new, and is a separate read and write, not one atomic step. The GVL does not make it atomic: it can switch threads between the read and the write, and the window is exactly the time new takes to run. If constructing the object takes time (opening a file, connecting, sleeping) several threads can pass the check before the first assignment, and each creates its own instance. The race is invisible in tests with one thread and appears under load:

# frozen_string_literal: true

# The classic hand-rolled singleton, with a slow constructor
# (opening a file, a connection...) that makes the race visible.
class NaiveRegistry
  @instance = nil

  class << self
    # "||=" is a read followed by a write: two steps, not one
    def instance = @instance ||= new

    private :new
  end

  def initialize
    sleep 0.05
  end
end

instances = Array.new(4) { Thread.new { NaiveRegistry.instance } }.map(&:value)
p instances.map(&:object_id).uniq.size
p instances.uniq.size
4
4

Source: Thread::Mutex

All four threads got different objects, so "singleton" is not guaranteed. The construction time was made long with sleep to make the failure reproducible; with a fast constructor the same code fails rarely, which is worse. The same ||= caveat applies to any lazily created class-level state, and to the memoized class << self singleton helpers found in many codebases.

Fix 1 and 2: create it eagerly, or lazily under a lock

The simplest correct version has no lazy step (EagerRegistry below). A constant is assigned while the file is loaded, and require holds a lock on the file, so the assignment happens exactly once. When the instance is expensive and may never be needed, take a Thread::Mutex around the check and the assignment (LazyRegistry), so only one thread runs the ||=.

# frozen_string_literal: true

# rubocop:disable Style/OneClassPerFile
# 1. Eager: the constant is created while the file loads, which is
#    synchronised by the require lock.
class EagerRegistry
  INSTANCE = new

  def self.instance = INSTANCE

  private_class_method :new
end

# 2. Lazy with a lock: check, then create, inside one critical section.
class LazyRegistry
  @lock = Thread::Mutex.new
  @instance = nil

  class << self
    def instance
      @lock.synchronize { @instance ||= new }
    end

    private :new
  end

  def initialize
    sleep 0.05
  end
end
# rubocop:enable Style/OneClassPerFile

eager = Array.new(4) { Thread.new { EagerRegistry.instance } }.map(&:value)
lazy = Array.new(4) { Thread.new { LazyRegistry.instance } }.map(&:value)
p eager.uniq.size
p lazy.uniq.size
1
1

Both variants return one object to all threads. Initialize the mutex in the class body, not lazily, otherwise the lock itself is racy. Taking the lock on every call is cheap in MRI; if it ever matters, check @instance first outside the lock and again inside (double-checked locking), which is what the standard library does.

Fix 3: the Singleton module

The singleton library (a default gem, version 0.3.0 in Ruby 4.0.7) packages the pattern. include Singleton makes new and allocate private, adds a thread-safe instance class method, blocks dup and clone, gives every subclass its own instance, and defines _dump/_load so that Marshal round-trips to the same instance. It implements the double-checked lock described above with a mutex per class.

# frozen_string_literal: true

require "singleton"

class Settings
  include Singleton

  attr_accessor :loan_days

  def initialize
    @loan_days = 14
    sleep 0.05
  end
end

instances = Array.new(4) { Thread.new { Settings.instance } }.map(&:value)
p instances.map(&:object_id).uniq.size
p Settings.instance.equal?(Settings.instance)

begin
  Settings.new
rescue NoMethodError => e
  puts e.message
end

begin
  Settings.instance.dup
rescue TypeError => e
  puts "#{e.class}: #{e.message}"
end

begin
  Settings.instance.clone
rescue TypeError => e
  puts "#{e.class}: #{e.message}"
end

p Settings.include?(Singleton)
p Settings.singleton_class.private_method_defined?(:new)
p Settings.singleton_class.public_method_defined?(:instance)
p Marshal.load(Marshal.dump(Settings.instance)).equal?(Settings.instance)
1
true
private method 'new' called for class Settings
TypeError: can't dup instance of singleton Settings
TypeError: can't clone instance of singleton Settings
true
true
true
true

Source: Singleton

  • The four threads share one object although the constructor sleeps.

  • Settings.new fails with NoMethodError ("private method 'new' called for class Settings") and Settings.allocate fails the same way; dup and clone raise TypeError naming the class.

  • Marshal.load(Marshal.dump(instance)) is the same object, because _load calls instance. Override _dump and _load to keep state across serialization.

  • Including Singleton in a module raises TypeError; extending an object with it is disabled. It targets classes only.

A subclass of a Singleton class is a separate singleton with its own instance and is initialized by the subclass’s initialize:

# frozen_string_literal: true

require "singleton"
require "shelf"

# rubocop:disable Style/OneClassPerFile
class Settings
  include Singleton

  attr_accessor :loan_days

  def initialize = @loan_days = 14
end

class StrictSettings < Settings
  def initialize
    super
    @loan_days = 7
  end
end

# Inject the collaborator instead of reaching for the global.
class Lender
  def initialize(settings: Settings.instance) = @settings = settings

  def days = @settings.loan_days
end
# rubocop:enable Style/OneClassPerFile

p Settings.instance.loan_days
p StrictSettings.instance.loan_days
p StrictSettings.instance.equal?(Settings.instance)

begin
  Settings.allocate
rescue NoMethodError => e
  puts e.message
end

p Lender.new.days
p Lender.new(settings: Shelf::Config.new(nil, 3)).days
14
7
false
private method 'allocate' called for class Settings
14
3

Source: Singleton

StrictSettings.instance is not Settings.instance. Note the Lender class: it takes its collaborator as a keyword argument whose default is the global. Callers and tests can pass a different object (here a Shelf::Config with three loan days) without touching the global, which is the single most useful habit when a Singleton exists. A singleton that tests must reset (by reaching into its instance variables or reloading the file) is a signal that the code under test should receive the object rather than look it up. The library also contains an undocumented Ractor-local variant; Ractors need their own treatment (see Ractors).

Fix 4: module-level state without an instance

Often what the Singleton pattern is used for needs no instance at all. A module with its own state and module methods is a one-of-a-kind object that Ruby gives you for free, and Shelf.config/Shelf.configure in the running example are exactly that: a module-level instance variable behind a reader, with configure yielding it.

# frozen_string_literal: true

require "shelf"

# Module-level state, as Shelf.config does: no instance, no new.
module Clock
  @offset = 0

  class << self
    attr_accessor :offset

    def now = Time.at(1_800_000_000 + offset).utc
  end
end

p Clock.now
Clock.offset = 3600
p Clock.now
p Clock.singleton_methods.sort
p Shelf.config.loan_days
p Shelf.configure { |config| config.loan_days = 21 }.loan_days
2027-01-15 08:00:00 UTC
2027-01-15 09:00:00 UTC
[:now, :offset, :offset=]
14
21

Clock has no instance, no new and no instance; Clock.now and Clock.offset= are singleton methods of a module (which is an object with a singleton class like any other). Shelf.configure returns the configuration, so the last line shows 21. The lazy @config ||= ... inside Shelf.config is the same racy idiom as above and is safe in practice only when the configuration is read once during start-up before threads exist; for state that is first touched concurrently, initialize it in the module body, as Clock does.

Approach Choose it when Trade-off

Eager constant (INSTANCE = new)

The object is cheap and always needed.

Built at load time; failure at require time.

Lazy with Thread::Mutex

Creation is expensive or optional.

A lock on every call (small).

include Singleton

You want the guarantees without writing them.

Global state; tests need injection; hides the lifecycle.

Module with state (extend self or class << self)

No object semantics are needed (configuration, registry, clock).

Cannot be subclassed or instantiated; still global.

Pass the object in (dependency injection)

Almost always, for anything tests or callers may replace.

One more parameter to thread through.

Class-level behavior in other languages

Ruby Closest equivalent elsewhere Difference

def self.build (class method)

Java and C# static methods; Swift static/class methods; Rust associated functions

A Ruby class method is a real method on a real object: inheritable, overridable with super, passable as Class#method, and can be created at runtime.

class << self

Python metaclass or @classmethod; Kotlin companion object

The singleton class is an ordinary class that can include modules and take visibility keywords.

Class-level instance variable

Java static field; Python class attribute; Kotlin companion property

Not inherited and not shared with subclasses; Ruby has no static fields.

@@name

Java static field shared by subclasses; Python class attribute shared by subclasses

Shares a variable down the hierarchy, with the trap that any subclass can rebind it.

extend Module

Kotlin companion object implementing an interface; Python mixin on the metaclass

Adds methods to one object, not just classes.

include Singleton

Kotlin object (a language-level singleton); C# Lazy<T> and static constructors; Java enum singleton

Not a language feature: a library module on top of private new, and it can be defeated with allocate and send.

For the Kotlin object declaration, the C# static members and the Python metaclasses, see the pages linked in the introduction; the Ruby model is closer to Python’s (everything is an object, including the class) than to Java’s, where statics belong to no object.

Pitfalls

  • private before def self.x does nothing. Use private_class_method or put the method in class << self (Lint/IneffectiveAccessModifier).

  • Constants in class << self are not the class’s constants. Define them outside the block.

  • @@var is shared down the hierarchy. Use a class-level instance variable plus a reader, initialized in inherited when each subclass needs its own.

  • @instance ||= new is not thread-safe. Use an eager constant, a mutex, the Singleton module, or injection.

  • def self.x inside an instance method defines a singleton method on that one instance each time it runs.

  • dup drops singleton methods and clone keeps them; Marshal.dump refuses objects that have them.

  • Singleton methods on frozen objects (including every Data instance) raise FrozenError; Integers, Symbols, Floats and frozen string literals cannot have singleton classes at all.

  • private :new is not a lock. allocate and send(:new) bypass it.

  • Ractors. Class-level mutable state is the main thing that stops code being Ractor-safe.

RuboCop cops for this page

Cop What it checks

Style/ClassVars

Flags every @@ class variable (including class_variable_set) and recommends a class instance variable.

Lint/IneffectiveAccessModifier

private/protected followed by def self.x, which they do not affect.

Style/ClassMethods

def ClassName.x inside ClassName; use def self.x.

Style/ClassMethodsDefinitions

Disabled by default. Enforces def_self (def self.x) or self_class (class << self) for all class methods.

Lint/UselessAccessModifier

An access modifier that affects nothing, such as private as the last statement in a class body or in class << self.

Style/AccessModifierDeclarations

Group (private on its own line) or inline (private def x) style for access modifiers.

Style/TrivialAccessors

Hand-written readers and writers that should be attr_reader/attr_writer, including in class << self.

Style/ModuleFunction

extend self versus module_function.

Style/OneClassPerFile

Several top-level classes in one file (a new cop under NewCops: enable; examples that need it disable it).

Where older material is outdated

Books and blog posts that predate the current Ruby often show the Singleton pattern as @instance ||= new plus private_class_method :new, and class-level data as @@var, without noting that the first is not thread-safe and the second is shared with every subclass. The Singleton module, an eager constant, a mutex-guarded accessor or an injected collaborator are better today. The terminology also moved: the official documentation says "singleton class" (the informal "eigenclass" and "metaclass" still appear in older text), and singleton_class, Class#subclasses (3.1) and Class#attached_object (3.2) replace the older class << obj; self; end idiom and manual tracking of subclasses.

Version notes

Version Change relevant to this page

3.0

Reading or writing a class variable at the top level raises RuntimeError; a class variable overtaken by a superclass’s raises RuntimeError.

3.1

Class#subclasses.

3.2

Class#attached_object.

3.3

NoMethodError messages describe the receiver by its class, such as "for class Reader".

3.4

Error messages quote method names with apostrophes instead of a backtick and apostrophe.

4.0

No change to the singleton-class model. The singleton library is version 0.3.0.

Summary

  • A singleton class belongs to one object, sits in front of its class in the lookup path, and holds def obj.x, define_singleton_method and extended modules.

  • Class methods are singleton methods of the class object; they are inherited because singleton classes of subclasses inherit from singleton classes of superclasses.

  • def self.x, class << self and extend are three ways to the same place; private needs private_class_method or class << self.

  • Use class-level instance variables, not @@, and initialize per-subclass state in inherited.

  • The Singleton pattern is a design choice, separate from singleton classes. Make it thread-safe with an eager constant, a mutex or include Singleton, and prefer injecting the object.

References