Global Variables
$variable_name Global variables in Ruby are variables that can be accessed from anywhere in your program. They are denoted by a $ prefix and remain in scope throughout the entire execution of your program, from the moment they are defined until the program terminates.
characteristics of global variables
Unlike local variables which are confined to their lexical scope (the method, class, or block where they’re defined), global variables can be accessed and modified from any location in your code. This makes them powerful but also potentially dangerous if misused.
automatic global variables
Ruby provides several built-in global variables that are automatically available:
# $0 - The name of the current Ruby script being executed
puts $0 # => "my_script.rb" when running ruby my_script.rb
# $$ - The process ID of the current Ruby process
puts $$ # => 12345 (or whatever the PID is)
# $? - The exit status of the last child process
system("ls")
puts $?.success? # => true or false
# $ARGV / @ARGV - Command line arguments
# $LOAD_PATH - Array of directories to search for require
# $DEBUG - True if -d flag was used
# $VERBOSE - Verbosity level
These built-in names are the main reason Ruby developers still need to recognize global variables. They expose process information, interpreter state, and output handles that belong to the whole program. Treat user-defined globals with more caution. A name such as $request_count can be changed from any file, which makes bugs harder to trace when the program grows.
Common Uses
# Storing application-wide configuration
$config = { theme: "dark", language: "en" }
# Sharing state between methods
$request_count = 0
def handle_request
$request_count += 1
puts "Request ##{$request_count}"
end
For application code, a variable with the $ prefix is easiest to justify when the value truly belongs to the whole process. Even then, keep the name specific and avoid storing business data that could be passed through method arguments. Clear ownership matters because globals skip the usual boundaries between objects, methods, and modules, making it harder to reason about where and when a value changes.
Best Practices
avoid when possible
Global variables make code harder to test and reason about because any part of your program can modify them. Consider these alternatives:
# Instead of global variables, use constants for configuration
class Config
THEME = "dark"
MAX_CONNECTIONS = 100
end
# Or use class variables / class instance variables
class Database
@connection = nil
def self.connection
@connection ||= connect
end
end
Constants, class instance variables, and explicit configuration objects usually make the dependency easier to test. They also show readers where state is supposed to live. A global can feel convenient at first, but the cost appears later when a distant file changes it and the local code gives no clue that the change happened.
when global variables are acceptable
There are legitimate uses for global variables:
# Debugging flags
$DEBUG_MODE = true
# Global state for gems/libraries
$stdout.sync = true # Ruby built-in global for output sync
# Singleton pattern with globals (limited use cases)
$instance ||= MyClass.new
When you do use a global, document the reason near the assignment and keep writes in as few places as possible. Reads are easier to tolerate than writes, because repeated writes turn the variable into hidden shared state. For long-running programs, also consider thread behavior before using a global counter or cache.
Reading and Writing
$-prefixed variables can be assigned and read from any scope in the program, including inside methods, classes, and blocks. They do not require a declaration before use: accessing an undefined $ variable returns nil rather than raising a NameError. This permissiveness is convenient for small scripts but becomes a source of confusion in larger codebases because a misspelled name silently returns nil instead of failing early. For that reason, most Ruby style guides recommend restricting $ variables to the small set of built-in names provided by the interpreter.
# Assignment
$my_global = "Hello"
# Reading
puts $my_global
# Can be read before assignment (returns nil)
puts $undefined_global # => nil
The main takeaway from the example above is that $-prefixed variables need no special setup beyond the assignment itself. Any line of code in the same process can read or write them, which makes them convenient for small scripts but risky for libraries and larger applications. Before reaching for a $ variable, ask whether a constant, an instance variable on a shared object, or a configuration class would make the data flow easier to follow for the next person reading the code.
Caveats
# Global variables are not thread-local by default
$counter = 0
threads = 10.times.map do
Thread.new { 1000.times { $counter += 1 } }
end
threads.each(&:join)
puts $counter # => 10000 (but could vary due to race conditions!)
While $-prefixed variables can be useful for truly process-wide state like logging configuration or debug flags, prefer passing data explicitly through method arguments or using dependency injection for better, more maintainable code.