Skip to main content
โšก Calmops

Class Variables vs Class Instance Variables in Ruby

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

Resources

Comments