Instance Methods and Class Methods
Every method you define in a Ruby class belongs to one of two worlds: it either runs on individual objects created from that class (an instance method), or it runs on the class itself, treated as an object in its own right (a class method). Understanding the difference — and why Ruby even lets you call methods "on a class" — is the key to writing well-organized object-oriented Ruby, from simple data objects to factory methods and shared utilities.
Overview: How Methods Work in Ruby
In Ruby, everything is an object — 42.class returns Integer, nil.class returns NilClass, and crucially, String.class returns Class. That last fact matters here: a class is itself an object, an instance of the built-in class Class. Once you accept that, instance methods and class methods stop being two unrelated features and become one consistent idea: every method is defined on some object, and called on that object (or its instances).
An instance method is defined with def method_name inside a class body. It becomes available on every object created with ClassName.new, and it operates on that specific object’s state — its instance variables, the ones starting with @. When you call book1.describe, Ruby searches for describe starting at book1‘s singleton class, then Book, then any modules Book includes, then Book‘s superclass, and onward up the ancestor chain until it finds a match, or reaches BasicObject and raises NoMethodError. This chain is exactly what makes mixins, inheritance, and super work; Book.ancestors will print the exact search order Ruby uses.
A class method is defined with def self.method_name (or, equivalently, def Book.method_name) inside the class body. While Ruby evaluates a class body, self refers to the class itself, so this defines a method directly on Book‘s own singleton class — a hidden, per-object class that every object in Ruby has, including classes. That is why class methods are invisible to instances (book1.total_books fails) and instance methods are invisible on the class (Book.describe fails): they live in two different objects’ method tables. Class methods are the right tool for behavior that concerns the class as a whole rather than any single instance: factory methods that build objects a particular way, counters or registries that track instances, and utility methods that need no specific object’s state.
Syntax
class ClassName
def instance_method_name(args)
# runs on an instance; can read/write @instance_variables
end
def self.class_method_name(args)
# runs on the class itself
end
class << self
def another_class_method(args)
# grouped class methods; equivalent to def self.another_class_method
end
end
end
| Form | Defines | Called via |
|---|---|---|
def greet |
Instance method | object.greet |
def self.greet |
Class method | ClassName.greet |
def ClassName.greet |
Class method (equivalent, less common) | ClassName.greet |
class << self ... end |
A block for grouping several class methods | ClassName.method_name |
Examples
Example 1: An instance method and a class method that counts instances
The @@total_books class variable is shared by the class and every instance. Each call to initialize (an instance method) increments it, and the class method self.total_books reads it back.
class Book
attr_accessor :title, :author
@@total_books = 0
def initialize(title, author)
@title = title
@author = author
@@total_books += 1
end
def describe
"#{title} by #{author}"
end
def self.total_books
@@total_books
end
end
book1 = Book.new("Dune", "Frank Herbert")
book2 = Book.new("Neuromancer", "William Gibson")
puts book1.describe
puts book2.describe
puts Book.total_books
Output:
Dune by Frank Herbert
Neuromancer by William Gibson
2
describe is an instance method: each call operates on the receiver’s own @title/@author. total_books is a class method: it is only callable on Book itself, and it reports state (@@total_books) shared across every instance, not any single book’s data.
Example 2: A class method as a factory, grouped with class << self
Factory methods are class methods that build and return an instance, often after doing some conversion the plain initialize shouldn’t have to know about.
class Temperature
def initialize(celsius)
@celsius = celsius
end
def to_fahrenheit
@celsius * 9.0 / 5 + 32
end
class << self
def from_fahrenheit(f)
new((f - 32) * 5.0 / 9)
end
end
end
temp = Temperature.from_fahrenheit(98.6)
puts temp.to_fahrenheit.round(2)
Output:
98.6
Inside class << self ... end, self is Temperature‘s singleton class, so any def in that block defines a class method — identical in effect to writing def self.from_fahrenheit, just easier to read when you have several class methods together. Note that inside from_fahrenheit, calling bare new(...) works because the implicit receiver is Temperature itself.
Example 3: A registry built from class instance variables
This example tracks every Product ever created using @all, a class instance variable (a single @, owned by the Product object itself, not shared with subclasses the way @@ is).
class Product
@all = []
class << self
attr_reader :all
def register(product)
@all << product
end
def most_expensive
all.max_by(&:price)
end
end
attr_reader :name, :price
def initialize(name:, price:)
@name = name
@price = price
self.class.register(self)
end
end
Product.new(name: "Keyboard", price: 45)
Product.new(name: "Monitor", price: 220)
Product.new(name: "Mouse", price: 25)
puts Product.most_expensive.name
puts Product.all.map(&:name).join(", ")
Output:
Monitor
Keyboard, Monitor, Mouse
Every Product.new call runs the instance method initialize, which calls self.class.register(self) — an instance method reaching back out to a class method to record itself. attr_reader :all, written inside class << self, generates a class method that reads the class instance variable @all. This pattern (a registry of every instance, built from class methods and a class instance variable) is common in real Ruby code, from ORMs to plugin systems.
How It Works Step by Step
Tracing Book.new("Dune", "Frank Herbert") from Example 1 shows exactly what Ruby does:
Book.newis itself a class method (inherited fromClass) that allocates a blankBookobject, then callsinitializeon it with the given arguments.- Inside
initialize,selfis bound to the new object, so@titleand@authorare set on that specific instance, while@@total_books += 1updates the class variable shared byBookand every instance. book1.describeis called: Ruby checksbook1‘s singleton class (empty), thenBook, where it findsdescribe. It runs withselfbound tobook1, sotitleandauthorresolve through theattr_accessor-generated readers tobook1‘s own data.Book.total_booksis called: the receiver is theBookclass object itself, so Ruby searchesBook‘s singleton class first — wheretotal_bookslives, because it was defined withdef self.— and runs it withselfbound toBook, returning@@total_books.
Common Mistakes
Mistake 1: Forgetting self. when calling a setter inside a method
Inside a method, an assignment like balance = ... is always parsed as creating a local variable, never as calling a balance= setter — even if an attr_accessor generated one.
class Account
attr_accessor :balance
def initialize(balance)
@balance = balance
end
def apply_interest(rate)
balance = balance * (1 + rate)
end
end
acct = Account.new(100)
acct.apply_interest(0.1)
puts acct.balance
Output:
100
The right-hand side balance correctly calls the reader, but the left-hand side balance = silently creates a brand-new local variable that shadows the setter and is discarded when the method returns. @balance is never touched. The fix is to make the receiver explicit:
class Account
attr_accessor :balance
def initialize(balance)
@balance = balance
end
def apply_interest(rate)
self.balance = balance * (1 + rate)
end
end
acct = Account.new(100)
acct.apply_interest(0.1)
puts acct.balance
Output:
110.0
self.balance = ... unambiguously calls the setter method, so it actually updates @balance.
Mistake 2: Defining an instance method when you meant a class method
Leaving off self. defines a method on instances, not on the class — a very common typo when writing what was meant to be a class-level counter.
class Counter
@@count = 0
def increment_count
@@count += 1
end
def count
@@count
end
end
Counter.increment_count
Output:
NoMethodError: undefined method 'increment_count' for Counter:Class
increment_count was defined with a plain def, so it only exists on instances of Counter, never on Counter itself. Calling it directly on the class raises NoMethodError. Prefixing both methods with self. fixes it:
class Counter
@@count = 0
def self.increment_count
@@count += 1
end
def self.count
@@count
end
end
Counter.increment_count
Counter.increment_count
puts Counter.count
Output:
2
Best Practices
- Use instance methods for behavior that depends on one object’s own state; use class methods for behavior that concerns the class as a whole, like factories, registries, or counters.
- Prefer
class << self ... endwhen a class has several class methods — it reads clearly as "everything below is class-level" instead of repeatingself.on every line. - Reach for a class instance variable (
@count, read via a class-levelattr_reader) over a class variable (@@count) for most class-level state —@@variables are shared down the entire subclass hierarchy, which can surprise you the moment a subclass appears. - Inside an instance method, always call setters as
self.attribute = value, never bareattribute = value, to avoid accidentally creating a local variable. - Let
attr_accessor/attr_reader/attr_writergenerate simple getters and setters instead of hand-writing them. - Name factory-style class methods for what they build, like
User.from_jsonorPoint.origin, so the call site reads like a sentence. - Remember that
0,"", and[]are all truthy in Ruby — onlynilandfalseare falsy, which matters if a method (instance or class) returns one of these as a value another piece of code checks withif.
Practice Exercises
- Write a class
Dogwith an instance methodbarkthat returns"Woof!", and a class methodself.speciesthat returns"Canis familiaris". CallDog.species, then predict (and explain) what happens if you tryDog.new.speciesinstead. - Extend the
Productregistry from Example 3 with a class methodself.countthat returns how many products exist, usingall.size. Create three products and confirmProduct.countprints3. - Give a
Circleclass an instance methodarea(usingMath::PI * radius ** 2) and a class methodself.unit_circlethat returns a newCirclewith radius1, following the factory pattern from theTemperatureexample. ConfirmCircle.unit_circle.area.round(2)prints3.14.
Summary
- Instance methods (
def name) run on objects made with.newand operate on that object’s own@instance_variables. - Class methods (
def self.name, or several grouped insideclass << self ... end) run on the class object itself. - A class is an object too — an instance of
Class— so a class method is really just a method defined on that particular object’s singleton class. - Method lookup for instances walks the ancestor chain (singleton class, class, included modules, superclasses); class methods live on the class’s own singleton class.
- Use class methods for factories, registries, and class-wide state; use instance methods for behavior tied to one object.
- Always write
self.attribute = valuefor setters inside instance methods, and double-check you usedself.when you meant a class method — both are classic sources of bugs.
