rubyguides

Object#frozen?

obj.frozen? -> true or false

The frozen? method checks whether an object is frozen. A frozen object cannot be modified, and any attempt to change its state will raise a FrozenError. This is useful for immutability patterns, protecting constants, and keeping shared data predictable.

Syntax

object.frozen?

Calling frozen? returns a boolean value that tells you whether an object can be modified at this point in the program. The method is a predicate — its name ends with a question mark, which is Ruby’s convention for methods that return true or false. Some objects like symbols and integers are always frozen by the runtime, while others like strings and arrays start out mutable and can be frozen on demand with the freeze method. The sections below walk through how to check frozen state, what objects are frozen by default, and when to freeze your own data.

Parameters

This method takes no parameters.

Examples

Basic usage

str = "hello"
str.frozen?
# => false

sym = :hello
sym.frozen?
# => true

That first check is the simplest way to see the difference between a mutable object and one that has already been locked down. Symbols are frozen by nature, while regular strings can still be changed unless you freeze them explicitly.

Understanding frozen literals

# String literals are frozen in Ruby 3.0+
"hello".frozen?
# => true

# Variables holding frozen objects can be reassigned
a = "hello"
a.frozen?
# => true

a = "world"  # reassignment works
a.frozen?
# => true

Frozen literals are convenient because they let Ruby reuse the same object safely. If you need to change the value, duplicate it first and then work on the copy instead of the original literal. While literals are frozen automatically in modern Ruby, objects created at runtime start out mutable and must be frozen explicitly when immutability is desired. The next example demonstrates freeze on an array and shows what happens when code tries to modify a frozen collection:

Freezing objects explicitly

arr = [1, 2, 3]
arr.frozen?
# => false

arr.freeze
arr.frozen?
# => true

# Attempting to modify raises FrozenError
arr << 4
# => FrozenError (array is frozen)

Freezing the array itself protects the container, not just the values inside it. That distinction matters when the array is shared across methods and you want the shape of the collection to stay fixed. The elements themselves remain mutable unless you freeze them individually as well. In Ruby 3.0 and later, string literals are frozen by default, which changes the behavior of frozen? for literal strings compared to dynamically created ones. The next example explores how Ruby’s string deduplication interacts with the frozen state:

Checking object identity

a = "hello"
b = "hello"

# In Ruby, identical string literals may share the same object
a.object_id == b.object_id
# => true (depends on Ruby's internal string deduplication)

a.frozen?
# => true (string literals are frozen)

This example is mostly about showing that frozen values can still be shared efficiently. The literal may be reused by Ruby, but the frozen state is what makes that reuse safe. The three examples above cover the basic mechanics of checking and applying the frozen state at the object level. The next section moves from isolated examples to reusable patterns that combine frozen? with other Ruby idioms to solve real problems in application code.

Common Patterns

Protecting constants

CONFIG_KEYS = %w[api_key secret token].freeze

# Without freeze, someone could do:
# CONFIG_KEYS << "admin"  # would work!

CONFIG_KEYS.frozen?
# => true

That pattern is common in configuration code because constants usually should not change after boot. Freezing makes the intent visible and reduces the chance of accidental mutation later. Ruby constants themselves are not truly immutable — the interpreter issues a warning when you reassign one but does not prevent it — so calling freeze on a constant’s value is the only way to actually lock the data down. Once a constant holds a frozen collection, any code that tries to modify it will get a FrozenError at the point of the mutation rather than silently corrupting shared state.

Conditional modification

def process_data(data)
  # Work with a copy if the original is frozen
  working_copy = data.frozen? ? data.dup : data
  working_copy << "processed"
end

original = "test".freeze
process_data(original)
# => "testprocessed" (original unchanged)

Checking frozen? before mutating lets you decide whether to work on the object itself or a copy of it. That keeps the calling code flexible while still respecting immutability when the original value is shared. This guard pattern appears frequently in library code that accepts objects from callers and needs to modify them internally without surprising the caller with a mutated argument. The next pattern is a lighter variation that simply raises an error when a frozen object is passed where mutation is expected, failing fast rather than silently duplicating:

Debugging frozen state

def assert_mutable!(object)
  raise "Object is frozen!" if object.frozen?
end

This kind of helper is handy in code paths that expect mutation and want to fail fast if the object has already been locked down. The check itself is simple, but it can save time when debugging hidden state changes.

Understanding Ruby’s freezing mechanism

Ruby’s freezing mechanism is fundamental to its object model. When you freeze an object, you prevent any modifications to its internal state. This is particularly important in concurrent programming where multiple threads might access the same data structures.

In Ruby 3.0 and later, string literals are automatically frozen by default. This change improved performance and encouraged immutability. You can still create mutable strings dynamically, but string literals are frozen unless you explicitly duplicate them.

The freeze method affects the object itself, not the variable holding it. This means you can reassign a variable pointing to a frozen object, but you cannot modify the frozen object through any reference.

Performance Implications

Frozen objects can be more memory-efficient because Ruby can deduplicate identical frozen strings. When multiple variables hold the same string value, Ruby may store only one copy in memory, with all variables referencing the same object. This reduces memory usage significantly in applications with many string literals.

Frozen objects can sometimes be processed faster because the runtime does not need to worry about concurrent modifications. In multi-threaded applications, this can lead to measurable speed-ups when the same data is accessed from several threads simultaneously.

When you choose to freeze a value, the main payoff is clarity. The object stops acting like something that can be casually edited, and that makes the code around it easier to trust.

Errors

FrozenError

When you try to modify a frozen object, Ruby raises a FrozenError:

arr = [1, 2, 3].freeze
arr << 4
# => FrozenError (array is frozen)

The error message includes the object that was frozen and provides context for debugging.

See Also