Skip to main content
โšก Calmops

Class Variables in Ruby: @@var Explained

Introduction

Class variables in Ruby are prefixed with @@ and are shared across the class that defines them and all its subclasses. They’re one of Ruby’s most misunderstood features โ€” powerful in specific use cases, but dangerous when misused. This guide covers how they work, their pitfalls, and when to use them versus class instance variables.

Basic Usage

class HelloCount
  @@count = 0

  def self.count
    @@count
  end

  def initialize(myname = "Ruby")
    @name = myname
  end

  def hello
    @@count += 1
    puts "Hello, world, I am #{@name}"
  end
end

bob   = HelloCount.new("Bob")
alice = HelloCount.new("Alice")
ruby  = HelloCount.new

p HelloCount.count  # => 0

bob.hello    # => Hello, world, I am Bob
alice.hello  # => Hello, world, I am Alice
ruby.hello   # => Hello, world, I am Ruby

p HelloCount.count  # => 3

@@count is shared across all instances of HelloCount. Every call to hello increments the same counter.

Accessing Class Variables

Class variables can be read and written from:

  • Class methods (def self.method)
  • Instance methods (def method)
  • The class body itself
class Config
  @@debug = false
  @@version = "1.0.0"

  # Read from class method
  def self.debug?
    @@debug
  end

  # Write from class method
  def self.enable_debug!
    @@debug = true
  end

  # Read from instance method
  def version
    @@version
  end
end

Config.debug?         # => false
Config.enable_debug!
Config.debug?         # => true
Config.new.version    # => "1.0.0"

The Inheritance Problem

The most important thing to understand about @@var: it is shared across the entire inheritance hierarchy. Any subclass can read and modify it, and changes are 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 variable, it affects the parent:

class Vehicle
  @@wheels = 4
  def self.wheels; @@wheels; end
end

class Motorcycle < Vehicle
  @@wheels = 2  # modifies Vehicle's @@wheels too!
end

puts Vehicle.wheels     # => 2  (changed by Motorcycle!)
puts Motorcycle.wheels  # => 2

This is why class variables are sometimes called “global variables with a class-scoped name.”

Thread Safety

Class variables are not thread-safe. If multiple threads increment @@count simultaneously, you can get race conditions:

class Counter
  @@count = 0

  def self.increment
    @@count += 1  # NOT atomic โ€” read-modify-write race condition
  end

  def self.count
    @@count
  end
end

# In a multi-threaded environment, this can produce wrong results
threads = 100.times.map { Thread.new { Counter.increment } }
threads.each(&:join)
puts Counter.count  # might not be 100!

Fix with a Mutex:

class Counter
  @@count = 0
  @@mutex = Mutex.new

  def self.increment
    @@mutex.synchronize { @@count += 1 }
  end

  def self.count
    @@count
  end
end

Inspecting Class Variables

class MyClass
  @@foo = 1
  @@bar = 2
end

MyClass.class_variables          # => [:@@foo, :@@bar]
MyClass.class_variable_get(:@@foo)  # => 1
MyClass.class_variable_set(:@@foo, 99)
MyClass.class_variable_defined?(:@@foo)  # => true

When Class Variables Make Sense

Despite their pitfalls, class variables are appropriate in a few cases:

1. Truly shared counters across a hierarchy

class ActiveRecord::Base
  @@connection_pool = ConnectionPool.new
  # All models share the same connection pool โ€” intentional
end

2. Configuration shared across all subclasses

class APIClient
  @@base_url = "https://api.example.com"
  @@timeout  = 30

  def self.base_url; @@base_url; end
  def self.timeout;  @@timeout;  end
end

class UserClient < APIClient; end
class OrderClient < APIClient; end

# All clients share the same base URL and timeout
puts UserClient.base_url   # => "https://api.example.com"
puts OrderClient.timeout   # => 30

Class Variables vs Class Instance Variables

In most cases, class instance variables (@var) are the better choice:

# Class variable โ€” shared, dangerous
class BadCounter
  @@count = 0
  def self.count; @@count; end
  def self.increment; @@count += 1; end
end

class BadSubCounter < BadCounter; end

BadCounter.increment
puts BadSubCounter.count  # => 1  (shared โ€” probably not what you want)

# Class instance variable โ€” per-class, safe
class GoodCounter
  @count = 0
  def self.count; @count ||= 0; end
  def self.increment; @count = count + 1; end
end

class GoodSubCounter < GoodCounter
  @count = 0  # own counter
end

GoodCounter.increment
puts GoodSubCounter.count  # => 0  (independent)
puts GoodCounter.count     # => 1

Comparison Table

Feature @@class_variable @class_instance_variable
Shared with subclasses Yes โ€” all share one variable No โ€” each class has its own
Subclass can change parent Yes No
Accessible from instance methods Yes (directly) No (need self.class.@var)
Thread safety No (needs Mutex) No (needs Mutex)
Recommended Rarely Usually preferred

The Recommendation

Prefer class instance variables (@var) over class variables (@@var) in almost all cases. The Ruby Style Guide explicitly recommends avoiding class variables due to their surprising inheritance behavior.

# Avoid this
class MyClass
  @@config = {}
end

# Prefer this
class MyClass
  @config = {}
  def self.config; @config; end
end

Resources

Comments