Overview
Instance variables in Ruby begin with the @
symbol and store data specific to individual object instances. Ruby creates these variables when first assigned and maintains them throughout the object's lifetime. Unlike class variables or global variables, instance variables remain isolated within each object instance.
Ruby handles instance variables differently from local variables and class variables. The interpreter creates instance variables dynamically during assignment, without requiring explicit declaration. Each object maintains its own instance variable namespace, preventing conflicts between objects of the same class.
class Person
def initialize(name)
@name = name # Instance variable created on assignment
end
def name
@name # Accessing the instance variable
end
end
person1 = Person.new("Alice")
person2 = Person.new("Bob")
puts person1.name # => "Alice"
puts person2.name # => "Bob"
Ruby instance variables serve as the primary mechanism for object state management. The variables persist across method calls within the same object, maintaining data integrity and enabling stateful object behavior.
class Counter
def initialize
@count = 0
end
def increment
@count += 1
end
def value
@count
end
end
counter = Counter.new
counter.increment
counter.increment
puts counter.value # => 2
Instance variables support all Ruby data types including primitives, objects, collections, and custom classes. Ruby stores references to objects rather than copying values, enabling efficient memory usage for complex data structures.
class DataContainer
def initialize
@string = "text"
@number = 42
@array = [1, 2, 3]
@hash = { key: "value" }
@object = Time.now
end
def data
{
string: @string,
number: @number,
array: @array,
hash: @hash,
object: @object
}
end
end
Basic Usage
Instance variables store object state through assignment using the @variable_name
syntax. Ruby creates the variable when first assigned and maintains the value throughout the object's lifecycle. Access patterns follow standard variable syntax without additional method calls.
class BankAccount
def initialize(initial_balance)
@balance = initial_balance
@transactions = []
end
def deposit(amount)
@balance += amount
@transactions << { type: :deposit, amount: amount, time: Time.now }
end
def withdraw(amount)
@balance -= amount
@transactions << { type: :withdraw, amount: amount, time: Time.now }
end
def balance
@balance
end
def transaction_history
@transactions
end
end
account = BankAccount.new(1000)
account.deposit(250)
account.withdraw(100)
puts account.balance # => 1150
Ruby instance variables maintain separate values for each object instance. Modifying an instance variable in one object does not affect the same variable in other objects of the same class.
class Temperature
def initialize(celsius)
@celsius = celsius
end
def celsius=(value)
@celsius = value
end
def celsius
@celsius
end
def fahrenheit
(@celsius * 9.0 / 5.0) + 32
end
end
temp1 = Temperature.new(25)
temp2 = Temperature.new(30)
temp1.celsius = 20
puts temp1.celsius # => 20
puts temp2.celsius # => 30 (unchanged)
puts temp1.fahrenheit # => 68.0
puts temp2.fahrenheit # => 86.0
Uninitialized instance variables return nil
when accessed. Ruby does not raise exceptions for undefined instance variables, making conditional logic and default value patterns straightforward.
class ConfigManager
def username
@username ||= "guest" # Default value pattern
end
def username=(value)
@username = value
end
def password_set?
!@password.nil? # Check if instance variable exists
end
def set_password(value)
@password = value
end
end
config = ConfigManager.new
puts config.username # => "guest"
puts config.password_set? # => false
config.username = "admin"
config.set_password("secret")
puts config.username # => "admin"
puts config.password_set? # => true
Instance variable access requires method definitions for external visibility. Ruby does not provide automatic getter or setter methods, requiring explicit method definitions or attr_* helpers.
class Product
def initialize(name, price)
@name = name
@price = price
@created_at = Time.now
end
# Manual getter methods
def name
@name
end
def price
@price
end
# Manual setter with validation
def price=(new_price)
raise ArgumentError, "Price must be positive" unless new_price > 0
@price = new_price
end
# Read-only access
def created_at
@created_at
end
end
product = Product.new("Widget", 29.99)
puts product.name # => "Widget"
product.price = 34.99
puts product.price # => 34.99
# product.created_at = Time.now # Would raise NoMethodError
Advanced Usage
Instance variables support complex data structures and object composition patterns. Ruby handles nested objects, collections, and references without special syntax requirements. Deep object structures maintain proper reference semantics.
class OrderManager
def initialize
@orders = {}
@customer_cache = {}
@order_counter = 0
end
def create_order(customer_id, items)
@order_counter += 1
order_id = "ORD#{@order_counter.to_s.rjust(6, '0')}"
@orders[order_id] = {
customer_id: customer_id,
items: items.map { |item| item.dup }, # Defensive copying
status: :pending,
created_at: Time.now,
total: calculate_total(items)
}
cache_customer(customer_id)
order_id
end
def update_order_status(order_id, status)
return false unless @orders.key?(order_id)
@orders[order_id][:status] = status
@orders[order_id][:updated_at] = Time.now
true
end
def orders_for_customer(customer_id)
@orders.select { |_, order| order[:customer_id] == customer_id }
end
private
def calculate_total(items)
items.sum { |item| item[:price] * item[:quantity] }
end
def cache_customer(customer_id)
@customer_cache[customer_id] ||= fetch_customer_data(customer_id)
end
def fetch_customer_data(customer_id)
# Simulate customer data retrieval
{ id: customer_id, last_accessed: Time.now }
end
end
Metaprogramming techniques work seamlessly with instance variables. Ruby provides introspection methods and dynamic variable manipulation capabilities for advanced patterns.
class DynamicAttributeContainer
def initialize(attributes = {})
@attributes = {}
@validators = {}
attributes.each { |key, value| set_attribute(key, value) }
end
def define_attribute(name, type: nil, validator: nil)
@validators[name.to_sym] = { type: type, validator: validator }
# Define getter method
define_singleton_method(name) do
@attributes[name.to_sym]
end
# Define setter method
define_singleton_method("#{name}=") do |value|
set_attribute(name, value)
end
end
def set_attribute(name, value)
name_sym = name.to_sym
if @validators[name_sym]
validate_attribute(name_sym, value)
end
@attributes[name_sym] = value
end
def get_attribute(name)
@attributes[name.to_sym]
end
def attribute_names
@attributes.keys
end
def to_h
@attributes.dup
end
private
def validate_attribute(name, value)
validator_info = @validators[name]
if validator_info[:type] && !value.is_a?(validator_info[:type])
raise TypeError, "#{name} must be of type #{validator_info[:type]}"
end
if validator_info[:validator] && !validator_info[:validator].call(value)
raise ArgumentError, "#{name} failed validation"
end
end
end
container = DynamicAttributeContainer.new
container.define_attribute(:age, type: Integer, validator: ->(v) { v >= 0 })
container.define_attribute(:email, type: String, validator: ->(v) { v.include?('@') })
container.age = 25
container.email = "user@example.com"
puts container.age # => 25
puts container.email # => "user@example.com"
Instance variables enable sophisticated state management patterns including state machines, observer patterns, and complex initialization sequences.
class StateMachine
def initialize(initial_state)
@current_state = initial_state
@state_history = [initial_state]
@state_transitions = {}
@state_callbacks = {}
end
def define_transition(from_state, to_state, &block)
@state_transitions[from_state] ||= {}
@state_transitions[from_state][to_state] = block
end
def define_callback(state, type, &block)
@state_callbacks[state] ||= {}
@state_callbacks[state][type] ||= []
@state_callbacks[state][type] << block
end
def transition_to(new_state)
return false unless can_transition_to?(new_state)
execute_callbacks(@current_state, :exit)
if @state_transitions[@current_state] && @state_transitions[@current_state][new_state]
@state_transitions[@current_state][new_state].call(self)
end
previous_state = @current_state
@current_state = new_state
@state_history << new_state
execute_callbacks(@current_state, :enter)
true
end
def current_state
@current_state
end
def state_history
@state_history.dup
end
def can_transition_to?(state)
@state_transitions[@current_state] && @state_transitions[@current_state].key?(state)
end
private
def execute_callbacks(state, type)
return unless @state_callbacks[state] && @state_callbacks[state][type]
@state_callbacks[state][type].each { |callback| callback.call(self) }
end
end
Common Pitfalls
Instance variable naming conflicts occur when subclasses use identical variable names as parent classes. Ruby does not scope instance variables to class hierarchies, leading to unexpected overwrites and data corruption.
class Vehicle
def initialize(brand)
@brand = brand
@status = "new"
end
def status
@status
end
end
class Car < Vehicle
def initialize(brand, model)
super(brand)
@model = model
@status = "ready" # Overwrites parent's @status
end
def model
@model
end
end
car = Car.new("Toyota", "Camry")
puts car.status # => "ready" (not "new" as might be expected)
# Better approach: use descriptive names
class ImprovedVehicle
def initialize(brand)
@vehicle_brand = brand
@vehicle_status = "new"
end
def status
@vehicle_status
end
end
class ImprovedCar < ImprovedVehicle
def initialize(brand, model)
super(brand)
@car_model = model
@car_status = "ready" # Different variable, no conflict
end
def model
@car_model
end
def car_status
@car_status
end
end
Shared object references between instance variables create unexpected mutation side effects. Ruby stores object references, not copies, causing changes to propagate across seemingly independent variables.
class ProblematicConfig
DEFAULT_OPTIONS = { timeout: 30, retries: 3 }
def initialize(custom_options = {})
@options = DEFAULT_OPTIONS.merge(custom_options) # Dangerous!
end
def update_option(key, value)
@options[key] = value
end
def options
@options
end
end
config1 = ProblematicConfig.new
config2 = ProblematicConfig.new
config1.update_option(:timeout, 60)
puts config2.options[:timeout] # => 60 (unexpected mutation!)
# Correct approach: defensive copying
class SafeConfig
DEFAULT_OPTIONS = { timeout: 30, retries: 3 }.freeze
def initialize(custom_options = {})
@options = DEFAULT_OPTIONS.dup.merge(custom_options)
end
def update_option(key, value)
@options[key] = value
end
def options
@options.dup # Return copy to prevent external mutation
end
end
safe_config1 = SafeConfig.new
safe_config2 = SafeConfig.new
safe_config1.update_option(:timeout, 60)
puts safe_config2.options[:timeout] # => 30 (correct isolation)
Instance variable typos create silent failures because Ruby returns nil
for undefined variables instead of raising exceptions. These bugs often remain undetected until runtime conditions expose the missing data.
class UserProfile
def initialize(name, email)
@name = name
@emial = email # Typo: should be @email
end
def email
@email # Returns nil due to typo above
end
def name
@name
end
def summary
"#{@name} (#{@email})" # Will show "()" due to nil email
end
end
profile = UserProfile.new("Alice", "alice@example.com")
puts profile.email # => nil (silent failure)
puts profile.summary # => "Alice ()" (broken output)
# Prevention strategies
class RobustUserProfile
def initialize(name, email)
self.name = name # Use setter methods with validation
self.email = email
end
def name=(value)
raise ArgumentError, "Name cannot be empty" if value.nil? || value.strip.empty?
@name = value.strip
end
def email=(value)
raise ArgumentError, "Email cannot be empty" if value.nil? || value.strip.empty?
raise ArgumentError, "Invalid email format" unless value.include?('@')
@email = value.strip.downcase
end
def name
@name
end
def email
@email
end
def summary
raise RuntimeError, "Profile incomplete" if @name.nil? || @email.nil?
"#{@name} (#{@email})"
end
end
Memory leaks occur when instance variables maintain references to large objects or create circular references that prevent garbage collection. Ruby's garbage collector cannot reclaim objects involved in reference cycles.
class LeakyCache
def initialize
@cache = {}
@parent_refs = {}
end
def store(key, large_object)
@cache[key] = large_object
# Creating circular reference - memory leak!
large_object.define_singleton_method(:cache_ref) { @cache }
@parent_refs[large_object] = self
end
def retrieve(key)
@cache[key]
end
end
# Better approach: weak references and explicit cleanup
class ManagedCache
def initialize(max_size: 1000)
@cache = {}
@access_times = {}
@max_size = max_size
end
def store(key, object)
cleanup_if_needed
@cache[key] = object
@access_times[key] = Time.now
end
def retrieve(key)
if @cache.key?(key)
@access_times[key] = Time.now
@cache[key]
end
end
def clear
@cache.clear
@access_times.clear
end
private
def cleanup_if_needed
return unless @cache.size >= @max_size
# Remove least recently accessed items
sorted_keys = @access_times.sort_by { |_, time| time }.map(&:first)
keys_to_remove = sorted_keys.first(@cache.size - @max_size + 1)
keys_to_remove.each do |key|
@cache.delete(key)
@access_times.delete(key)
end
end
end
Reference
Instance Variable Syntax
Syntax | Usage | Example |
---|---|---|
@variable |
Define and access instance variable | @name = "Alice" |
@variable = value |
Assignment | @count = 0 |
@variable ||= default |
Default value assignment | @items ||= [] |
@variable&.method |
Safe navigation | @user&.name |
Common Patterns
Pattern | Implementation | Use Case |
---|---|---|
Lazy Initialization | @var ||= expensive_computation |
Defer object creation |
Defensive Copying | @array = input.dup |
Prevent external mutation |
Validation Setter | def value=(v); validate(v); @value = v; end |
Data integrity |
Computed Property | @cached_result ||= compute_result |
Performance optimization |
Memory and Performance Characteristics
Aspect | Behavior | Notes |
---|---|---|
Creation | Dynamic on first assignment | No declaration required |
Access Speed | Direct memory lookup | Faster than method calls |
Memory Usage | One slot per variable per instance | Efficient storage |
Garbage Collection | Collected with object | Standard Ruby GC rules |
Thread Safety | Not thread-safe by default | Requires synchronization |
Introspection Methods
Method | Returns | Description |
---|---|---|
instance_variables |
Array<Symbol> |
List all instance variables |
instance_variable_get(:@var) |
Object |
Get variable value |
instance_variable_set(:@var, value) |
Object |
Set variable value |
instance_variable_defined?(:@var) |
Boolean |
Check if variable exists |
remove_instance_variable(:@var) |
Object |
Remove variable, return value |
Inheritance Behavior
Scenario | Result | Explanation |
---|---|---|
Parent defines @var | Child can access @var | Instance variables not scoped to class |
Child defines @var | May overwrite parent's @var | Same variable name conflicts |
Multiple inheritance | Variables from all ancestors | Include/prepend affects variable access |
Common Error Patterns
Error Type | Cause | Prevention |
---|---|---|
Typos | @emial instead of @email |
Use setter methods with validation |
Nil Access | Undefined variable returns nil | Check with instance_variable_defined? |
Shared References | Multiple variables reference same object | Use dup or clone for copying |
Memory Leaks | Circular references prevent GC | Implement explicit cleanup methods |
Thread Races | Concurrent access without synchronization | Use Mutex or atomic operations |
Best Practices Summary
Practice | Implementation | Benefit |
---|---|---|
Descriptive Names | @user_email not @email |
Avoid namespace conflicts |
Input Validation | Validate in setter methods | Prevent invalid state |
Defensive Copying | @data = input.dup |
Prevent external mutation |
Explicit Initialization | Set defaults in initialize |
Avoid nil-related bugs |
Documentation | Comment complex state management | Improve maintainability |