Frozen String Literals
Fast recall
Quick revision
Core answer
# frozen_string_literal: true tells Ruby to allocate static string literals in that source file once at parse time and freeze them. A literal such as "ready" cannot then be changed with <<, replace, or another mutating method; Ruby raises FrozenError. The directive is per file and must appear in the first comment section, normally on the first line or after a shebang. It does not deeply freeze containers or every string made at runtime, and interpolated string literals are not frozen by this directive in Ruby 3.0 and later. Use dup or create a new string when mutation is intentional.Whether you know # frozen_string_literal: true makes string literals immutable (frozen) per-file, and why (safety + perf).
Trying to mutate a frozen literal (<< or gsub! raises FrozenError), or expecting it to affect non-literal strings.
# frozen_string_literal: true (magic comment, top of file) makes string literals immutable. Prevents bugs; improves perf. Use .dup to mutate.
Interview-ready guide
Interview answer
Ruby String objects are normally mutable. The file-level directive # frozen_string_literal: true changes how static string literals in that file are created: Ruby allocates them once at parse time and freezes them, allowing repeated occurrences to reuse an immutable object.
Mutation methods such as <<, replace, upcase!, or indexed assignment then raise FrozenError for those literals. Methods such as upcase or + can still return a different String. When code intentionally needs a buffer, literal.dup makes a mutable copy and documents that choice.
For example, with the directive enabled, status = "ready" is frozen and status << "!" fails. message = status.dup; message << "!" works because message is a separate object. This prevents a helper from unexpectedly changing a label shared elsewhere and can avoid repeated literal allocations.
The scope is precise. A magic comment affects only its file and must be in the first comment section, normally the first line or the line after a shebang. It is not a recursive freeze for arrays or hashes, and Ruby 3.0 or newer does not freeze dynamic interpolated literals through this directive.
I use the setting consistently and treat a mutation failure as a signal to decide ownership. If the text is a fixed value, avoid mutation; if the operation truly builds text, create a mutable string deliberately. That is clearer than scattering unconditional dup calls around every literal.
Frozen String Literals
Direct Answer
The magic comment # frozen_string_literal: true tells Ruby to allocate static string literals in that source file once at parse time and freeze them. A literal such as "ready" cannot then be changed with <<, replace, or another mutating method; Ruby raises FrozenError. The directive is per file and must appear in the first comment section, normally on the first line or after a shebang. It does not deeply freeze containers or every string made at runtime, and interpolated string literals are not frozen by this directive in Ruby 3.0 and later. Use dup or create a new string when mutation is intentional.
Deep Dive & Architecture
A static string literal is text written directly in source code without runtime interpolation. With the directive enabled, Ruby creates that literal at parse time as a frozen object and can reuse it instead of allocating a fresh mutable object for each evaluation. Calling frozen? reveals the state. An attempt to change the same object fails because freezing is permanent for that object; it cannot later be unfrozen.
Mutation and transformation must be distinguished. label << '!', label[0] = 'R', and label.upcase! try to alter label itself and therefore raise for a frozen literal. label + '!' and label.upcase normally return another String and leave the source alone. dup explicitly copies the object into a mutable String when an in-place API is genuinely useful.
Placement and scope prevent surprising global behaviour. The magic directive is read from the first comment section of one file; a shebang may occupy the first line. Requiring that file from another source does not switch the caller's literal policy. Projects commonly enforce the comment consistently with a formatter or linter so developers do not need to guess the mode while reading each file.
The word “literal” is an important boundary. A String returned from I/O, a network library, or String.new follows the creation API's own rules. Starting with Ruby 3.0, a dynamically interpolated literal such as "order-#{id}" is neither frozen nor reused merely because this directive is enabled. Code should inspect the specific object contract instead of assuming all strings are immutable.
Freezing is also shallow. Freezing an array stops adding or replacing array entries, but does not automatically freeze String objects already inside it. Conversely, this string-literal directive freezes the literal strings but says nothing about a container built around them. Deeply immutable configuration requires deliberate construction of every nested object or a separate sharing policy.
Memory savings are a possible outcome, not a reason to mutate blindly around the rule. Shared immutable literals avoid some duplicate allocations and make accidental modification visible. The design question remains ownership: constants and labels should usually stay unchanged, while builders and parsers may need writable buffers. Making that distinction explicit yields safer code even when the performance difference is small.
Code Example
# frozen_string_literal: true
status = 'ready'
puts status.frozen? # true
message = status.dup
message << '!'
puts message # ready!
puts status # ready
order_id = 42
dynamic = "order-#{order_id}"
puts dynamic.frozen? # false on Ruby 3.0+
# status << '!' would raise FrozenError
Common Mistakes & Pitfalls
Trying to mutate a frozen literal (<< or gsub! raises FrozenError), or expecting it to affect non-literal strings.
Check your understanding
Answer these without looking back at the article.
- Why does `dup` allow mutation without changing the original literal?
- Which strings are outside the directive's scope?
- Why is freezing a container different from deep immutability?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.