Introduction
Ruby has two ways to store state at the class level: class variables (@@var) and class instance variables (@var). They look similar but behave very differently, especially with inheritance. Choosing the wrong one is a common source of subtle bugs.
The Basics
class A
# Class instance variable โ belongs to the class object A
@var = 1
# Class variable โ shared across A and all subclasses
@@var = 2
def self.class_instance_var; @var; end
def self.class_var; @@var; end
end
A.instance_variables # => [:@var]
A.class_variables # => [:@@var]
puts A.class_instance_var # => 1
puts A.class_var # => 2
Class Variables (@@var): Shared Across the Hierarchy
A class variable is shared between the class that defines it and all its subclasses. Any subclass can read and write it, and the change is visible everywhere.
class Animal
@@count = 0
def initialize
@@count += 1
end
def self.count
@@count
end
end
class Dog < Animal; end
class Cat < Animal; end
Dog.new
Dog.new
Cat.new
puts Animal.count # => 3
puts Dog.count # => 3 (same variable!)
puts Cat.count # => 3 (same variable!)
This shared behavior is often surprising. If a subclass reassigns the class variable, it affects the parent too:
class Vehicle
@@wheels = 4
def self.wheels; @@wheels; end
end
class Motorcycle < Vehicle
@@wheels = 2 # modifies the SAME @@wheels as Vehicle!
end
puts Vehicle.wheels # => 2 (changed by Motorcycle!)
puts Motorcycle.wheels # => 2
This is why class variables are sometimes described as “global variables with a class-scoped name” โ they behave like globals within the inheritance hierarchy.
Class Instance Variables (@var): Per-Class State
A class instance variable is an instance variable on the class object itself. Because each class is a separate object, each class gets its own copy โ subclasses do not share it.
class Animal
@count = 0
def initialize
self.class.increment_count
end
def self.count
@count
end
def self.increment_count
@count = (@count || 0) + 1
end
end
class Dog < Animal
@count = 0 # Dog's own @count, separate from Animal's
end
class Cat < Animal
@count = 0
end
Dog.new
Dog.new
Cat.new
puts Animal.count # => 0 (Animal's own @count, not incremented by subclasses)
puts Dog.count # => 2
puts Cat.count # => 1
Each class maintains its own counter independently.
Inheritance Behavior: Side-by-Side
class Base
@@class_var = "base_class_var"
@class_inst_var = "base_class_inst_var"
def self.class_var; @@class_var; end
def self.class_inst_var; @class_inst_var; end
end
class Child < Base
# Does NOT define its own @class_inst_var
end
puts Child.class_var # => "base_class_var" (inherited via @@)
puts Child.class_inst_var # => nil (Child has no @class_inst_var)
# Now modify in Child
class Child
@@class_var = "child_class_var" # modifies Base's @@class_var too!
@class_inst_var = "child_class_inst_var" # only affects Child
end
puts Base.class_var # => "child_class_var" (changed by Child!)
puts Base.class_inst_var # => "base_class_inst_var" (unchanged)
puts Child.class_inst_var # => "child_class_inst_var"
Practical Use Cases
Class Variables: Shared Counters or Config
Use @@var when you genuinely want all subclasses to share the same value:
class DatabaseConnection
@@pool_size = 5
@@connections = []
def self.pool_size
@@pool_size
end
def self.pool_size=(n)
@@pool_size = n
end
end
# All subclasses share the same pool config
class ReadConnection < DatabaseConnection; end
class WriteConnection < DatabaseConnection; end
DatabaseConnection.pool_size = 10
puts ReadConnection.pool_size # => 10
puts WriteConnection.pool_size # => 10
Class Instance Variables: Per-Class Configuration
Use @var when each class in the hierarchy should have its own independent value:
class Animal
@sound = "..."
def self.sound
@sound
end
def speak
"#{self.class.name} says: #{self.class.sound}"
end
end
class Dog < Animal
@sound = "Woof"
end
class Cat < Animal
@sound = "Meow"
end
class Duck < Animal
@sound = "Quack"
end
puts Dog.new.speak # => "Dog says: Woof"
puts Cat.new.speak # => "Cat says: Meow"
puts Duck.new.speak # => "Duck says: Quack"
puts Animal.new.speak # => "Animal says: ..."
This pattern is common in Rails โ ApplicationRecord uses class instance variables to store per-model configuration like table names, validations, and callbacks.
Inheriting Class Instance Variables
By default, subclasses don’t inherit class instance variables. But you can implement inheritance manually:
class Base
@config = { timeout: 30, retries: 3 }
def self.config
# Walk up the ancestor chain to merge configs
ancestors.select { |a| a.instance_variable_defined?(:@config) }
.reverse
.reduce({}) { |merged, klass| merged.merge(klass.instance_variable_get(:@config)) }
end
end
class Child < Base
@config = { retries: 5, debug: true }
end
puts Base.config.inspect
# => {:timeout=>30, :retries=>3}
puts Child.config.inspect
# => {:timeout=>30, :retries=>5, :debug=>true} (merged with parent)
Quick Reference
| Feature | @@class_variable |
@class_instance_variable |
|---|---|---|
| Defined on | Class and all subclasses | The class object itself |
| Shared with subclasses | Yes โ all share the same variable | No โ each class has its own |
| Subclass can modify parent’s value | Yes | No |
| Accessible from instance methods | Yes (via @@var) |
No (need self.class.instance_variable_get) |
| Risk of unexpected sharing | High | Low |
| Recommended for | Truly shared state | Per-class configuration |
The Recommendation
Prefer class instance variables (@var) over class variables (@@var) in most cases. The shared-across-hierarchy behavior of @@var is almost always surprising and leads to bugs. Class instance variables give you cleaner, more predictable per-class state.
# Prefer this:
class MyClass
@config = {}
def self.config; @config; end
end
# Over this (unless you truly need shared state):
class MyClass
@@config = {}
def self.config; @@config; end
end
Comments