Constants
A constant in Ruby is a name that starts with an uppercase letter and is meant to hold a value that doesn’t change — things like MAX_CONNECTIONS, PI, or configuration values. Constants make code more readable by giving a meaningful name to a fixed value, and they act as a single source of truth so you never have the same "magic number" scattered across a codebase. Ruby is unusual among languages in that constants are a naming convention enforced partially by the interpreter, not a hard guarantee — understanding exactly what Ruby does and doesn’t protect you from is the key to using them well.
Overview / How it works
In Ruby, any identifier that begins with an uppercase letter (A–Z) is treated as a constant by the parser. That’s the entire rule — the interpreter doesn’t care what value you assign to it, only that its name starts with a capital letter. By convention, Ruby programmers write multi-word constants in SCREAMING_SNAKE_CASE (MAX_RETRIES), while class and module names use CamelCase (PaymentProcessor) — both are constants as far as Ruby is concerned, which is why 1.class returns Integer and not integer. In fact, when you write class Foo; end, Ruby is roughly doing Foo = Class.new behind the scenes: the class itself is a value, and Foo is the constant that refers to it. This is a good example of Ruby’s "everything is an object" philosophy at work — classes and modules are just objects assigned to constants, exactly like any other constant.
Unlike languages such as Java or JavaScript’s const, Ruby does not raise an error if you reassign a constant. It prints a warning to the console ("already initialized constant") and lets the reassignment happen anyway. Real immutability — preventing the underlying object from being mutated in place — is a separate concern handled by calling freeze on the object, not by the constant mechanism itself.
Constants also have a scoping model distinct from local variables. A constant defined at the top level of a file lives on Object and is visible everywhere. A constant defined inside a class or module body is scoped to that class/module, and Ruby resolves references to it using lexical scoping: it looks at the modules/classes textually enclosing the point where the constant is referenced (innermost first), then walks up the ancestor chain (superclasses and included modules) of the innermost enclosing class, and finally checks the top level. This is why a method defined inside Physics::Ball can refer to GRAVITY directly if GRAVITY is defined in the lexically enclosing Physics module, but code outside that module must spell out the full path with the scope resolution operator, Physics::GRAVITY.
Syntax
CONSTANT_NAME = value
module ModuleName
OTHER_CONSTANT = value
end
ModuleName::OTHER_CONSTANT
| Part | Meaning |
|---|---|
CONSTANT_NAME |
Any identifier starting with an uppercase letter A–Z; convention is SCREAMING_SNAKE_CASE for values, CamelCase for classes/modules. |
= |
A normal assignment — constants are created and reassigned the same way as variables, just with a capitalized name. |
| Location | Defined at the top level, or directly inside a class/module body (not inside a method body — a method can only read a constant, not define one at the point it runs). |
Module::CONSTANT |
The scope resolution operator, used to reach a constant from outside its defining module/class. |
.freeze |
Called on the assigned object to make it immutable, since the constant mechanism alone does not protect the object’s internal state. |
private_constant :NAME |
Called inside the class/module body to hide a constant from code outside it, keeping it an implementation detail. |
Examples
Example 1: A basic constant
MAX_PLAYERS = 4
GAME_TITLE = "Ruby Quest"
puts "Welcome to #{GAME_TITLE}!"
puts "Maximum players allowed: #{MAX_PLAYERS}"
puts MAX_PLAYERS.class
Output:
Welcome to Ruby Quest!
Maximum players allowed: 4
Integer
Both MAX_PLAYERS and GAME_TITLE are ordinary variable assignments; the only thing that marks them as constants is the capital first letter. MAX_PLAYERS.class confirms that a constant is just a reference to a normal object — here, an Integer.
Example 2: Namespacing with modules and the scope resolution operator
module Physics
GRAVITY = 9.8
class Ball
def describe
"This ball falls at #{GRAVITY} m/s^2"
end
end
end
puts Physics::GRAVITY
puts Physics::Ball.new.describe
Output:
9.8
This ball falls at 9.8 m/s^2
GRAVITY is defined inside the Physics module, so from outside it must be reached as Physics::GRAVITY. Inside Ball#describe, though, Ruby finds GRAVITY without any prefix because Ball is lexically nested inside Physics — that nesting, not inheritance, is what makes the plain name visible there.
Example 3: Freezing a constant — and the shallow-freeze gotcha
CONFIG = {
name: "TaskFlow",
limits: { max_users: 100 }
}.freeze
puts CONFIG.frozen?
puts CONFIG[:limits].frozen?
CONFIG[:limits][:max_users] = 250
puts CONFIG[:limits][:max_users]
Output:
true
false
250
freeze only locks the object it’s called on — here, the outer hash. The nested hash stored under :limits is a completely separate object that was never frozen, so its contents can still be changed even though CONFIG itself reports frozen? == true. This "shallow freeze" behavior surprises many Ruby developers who assume freezing a constant deeply locks everything inside it.
How it works step by step
module Shapes
PI = 3.14159
class Circle
def initialize(radius)
@radius = radius
end
def area
PI * @radius ** 2
end
end
end
circle = Shapes::Circle.new(2)
puts circle.area
begin
puts PI
rescue NameError => e
puts "Error: #{e.message}"
end
Output:
12.56636
Error: uninitialized constant PI
- Ruby parses
class Circleas lexically nested insidemodule Shapes, soModule.nestinginside#areais[Shapes::Circle, Shapes]. - When
#areareferencesPI, Ruby checks each entry of that lexical nesting in order — it isn’t found directly onCircle, but it is found on the enclosingShapesmodule, so resolution succeeds and returns3.14159. circle.areaevaluates to3.14159 * 2 ** 2, which is12.56636, andputsprints it.- At the top level, there is no lexical nesting that includes
Shapes, so a bare reference toPIfails. Ruby raises aNameErrorwith the messageuninitialized constant PI, which therescueclause catches and prints.
Common Mistakes
Mistake 1: Assuming a constant’s contents can’t be mutated
Freezing the constant name doesn’t happen automatically — only the reference is fixed by convention, not the object it points to. Without an explicit freeze, an array or hash constant can still be mutated through any method that changes it in place, like <<.
DEFAULT_TAGS = ["ruby", "programming"]
def add_tag(tags, tag)
tags << tag
end
add_tag(DEFAULT_TAGS, "beginner")
puts DEFAULT_TAGS.inspect
SAFE_TAGS = ["ruby", "programming"].freeze
begin
add_tag(SAFE_TAGS, "beginner")
rescue FrozenError => e
puts "Error: #{e.class} - #{e.message}"
end
puts SAFE_TAGS.inspect
Output:
["ruby", "programming", "beginner"]
Error: FrozenError - can't modify frozen Array: ["ruby", "programming"]
["ruby", "programming"]
DEFAULT_TAGS silently grows because nothing stops << from mutating the array it points to. Calling .freeze on SAFE_TAGS turns the same mistake into a clear, catchable FrozenError instead of a silent bug. If you want a constant to truly never change, freeze it.
Mistake 2: Reassigning a constant and expecting an error
Reassigning a constant is legal Ruby — it just triggers a warning printed to standard error (something like warning: already initialized constant APP_NAME), not an exception. Code that relies on constants never changing can break quietly.
APP_NAME = "TaskFlow"
puts APP_NAME
APP_NAME = "TaskFlow Pro"
puts APP_NAME
Output:
TaskFlow
TaskFlow Pro
Both lines print successfully; the warning about the reinitialized constant goes to stderr, not stdout, so it’s easy to miss in normal usage. If a value is genuinely expected to change over the program’s lifetime, it isn’t a good fit for a constant — use a class variable, an instance variable, or a method instead.
Best Practices
- Write multi-word constants in
SCREAMING_SNAKE_CASEand reserveCamelCasefor class and module names. - Call
.freezeon constants that hold arrays, hashes, or other mutable objects, and remember thatfreezeis shallow — nested collections need their ownfreezeif they must be immutable too. - Group related constants inside a
moduleto namespace them instead of polluting the top level (Object). - Never reassign a constant after it’s meant to be set; if a value truly varies at runtime, it doesn’t belong as a constant.
- Use
private_constant :NAMEinside a class or module to hide constants that are implementation details, not part of the public API. - Prefer named constants over unexplained literal numbers or strings ("magic values") so intent is documented in the code itself.
Practice Exercises
- Write a module named
Physicscontaining a constantSPEED_OF_LIGHT(use299_792as a km/s approximation) and a method outside the module’s classes that prints it using the scope resolution operator. - Define a constant
SETTINGSas a frozen hash with a nested hash value. Show that the outer hash is frozen but the nested hash isn’t, then fix it so both levels resist mutation. - Predict, then check by tracing through the code by hand: what does the following print, and does it raise anything?
COUNT = 1; def show; puts COUNT; end; COUNT = 2; show
Summary
- Any identifier starting with an uppercase letter is a constant in Ruby — classes and modules are constants too, since they’re objects assigned to capitalized names.
- Ruby allows reassigning a constant; it only emits a warning to stderr, not an error, so don’t rely on constants being truly immutable by default.
- Use
.freezefor real immutability, and remember it’s shallow — it only locks the object it’s called on, not objects nested inside it. - Constant lookup follows lexical scoping first (the modules/classes textually enclosing the reference), then the ancestor chain, then the top level.
- Use
Module::CONSTANTto reach a constant from outside its defining module or class, andprivate_constantto keep internal constants hidden.
