Question

Ruby frozen_string_literal: true Explained for Beginners

Question

In a Ruby file such as this generated rspec binstub, what is the purpose of the magic comment # frozen_string_literal: true?

#!/usr/bin/env ruby
begin
  load File.expand_path("../spring", __FILE__)
rescue LoadError
end
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'rspec' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
  Pathname.new(__FILE__).realpath)

require "rubygems"
require "bundler/setup"

load Gem.bin_path("rspec-core", "rspec")

What is this comment intended to do, and how does it affect string literals in Ruby?

Short Answer

By the end of this page, you will understand what # frozen_string_literal: true means in Ruby, how it changes the behavior of string literals, why generated files often include it, and what kinds of errors or benefits it can introduce in everyday code.

Concept

In Ruby, strings are normally mutable by default. That means you can change their contents after they are created.

For example:

name = "Ruby"
name << " on Rails"
# => "Ruby on Rails"

The magic comment:

# frozen_string_literal: true

changes that behavior for string literals in that file.

With it enabled, string literals like:

"hello"
"rspec"

are automatically frozen, which means they cannot be modified.

If you try to change one, Ruby raises an error:

message = "hello"
message << " world"
# FrozenError

Why this matters

This feature helps with two common goals:

  • Safety: prevents accidental modification of string literals
  • Performance: Ruby can reuse immutable string objects more efficiently in some cases

Important detail

This comment affects string literals written directly in the file. It does not freeze every string object in your entire program.

For example, these are different ideas:

  • "hello" in a file with frozen string literals enabled → frozen
  • String.new("hello") → new mutable string unless you freeze it yourself
  • a string returned from a method → depends on how that string was created

Scope of the comment

The comment applies to the current Ruby file only. It does not automatically affect other files unless they also include the same comment.

That is why generated files like Bundler binstubs often include it explicitly: the file is declaring how string literals should behave inside that file.

Mental Model

Think of a string literal as a printed label.

Without # frozen_string_literal: true, Ruby gives you a label written in pencil, so you can erase or add to it.

With # frozen_string_literal: true, Ruby gives you a laminated label. You can read it and pass it around, but you cannot write on it.

So this:

"hello"

is no longer a changeable piece of text in that file. It becomes a read-only value.

That is useful when a string is meant to be a fixed constant like a file path, a key, a command name, or a message template.

Take Quiz

Syntax and Examples

The magic comment goes near the top of a Ruby file:

# frozen_string_literal: true

It is usually placed after the shebang line if there is one:

#!/usr/bin/env ruby
# frozen_string_literal: true

Example without frozen string literals

message = "hello"
message << " world"
puts message

Output:

hello world

This works because the string literal is mutable.

Example with frozen string literals

# frozen_string_literal: true

message = "hello"
message << " world"

Output:

FrozenError

Ruby raises an error because "hello" is frozen.

Creating a mutable copy

If you need to modify the string, create a new mutable copy:

# frozen_string_literal: true

message = "hello".dup
message << " world"
puts message

Output:

hello world

Explicitly freezing without the magic comment

Even without the file-level comment, you can freeze a single string:

message = "hello".freeze

The magic comment is just a file-wide shortcut for string literals.

Step by Step Execution

Consider this Ruby file:

# frozen_string_literal: true

greeting = "hi"
name = "Sam"
result = greeting + ", " + name
puts result

Step by step

1. Ruby reads the magic comment

# frozen_string_literal: true

Ruby enables frozen string literals for this file.

2. Ruby creates greeting

greeting = "hi"

Because the file has frozen string literals enabled, "hi" is frozen.

3. Ruby creates name

name = "Sam"

"Sam" is also frozen.

4. Ruby builds result

result = greeting + ", " + name

This works. The + operator creates a new string rather than modifying greeting directly.

So frozen strings can still be used in expressions.

5. Ruby prints the result

puts result

Output:

hi, Sam

What would fail?

This would fail:

# frozen_string_literal: true

greeting = "hi"
greeting << " there"

Why? Because << tries to modify the original string object, and frozen objects cannot be changed.

Real World Use Cases

# frozen_string_literal: true is useful in many real Ruby programs.

Generated files

Tools like Bundler often generate scripts containing many fixed strings:

  • gem names
  • file paths
  • environment variable keys
  • command names

Those strings are not meant to be edited at runtime, so freezing them is sensible.

Libraries and gems

Library code often uses many constant-like strings:

JSON_CONTENT_TYPE = "application/json"

Freezing literals helps avoid accidental mutation.

Rails apps

Rails applications may use many strings for:

  • parameter keys
  • route names
  • SQL fragments
  • logging messages
  • configuration values

Freezing literals can reduce accidental bugs and encourage safer string handling.

CLI scripts

Command-line tools often define fixed option names and messages:

puts "Usage: my_tool [options]"

Those strings are ideal candidates for freezing.

Performance-sensitive code

In code that creates many repeated string literals, immutability can reduce unnecessary object mutation and sometimes improve memory behavior.

Take Quiz

Real Codebase Usage

In real projects, developers use frozen string literals as part of a broader style of writing safer Ruby.

Common pattern: immutable defaults

# frozen_string_literal: true

DEFAULT_ROLE = "guest"
STATUS_OK = "ok"

These values are intended to stay unchanged.

Common pattern: duplicate before mutation

If a value starts from a literal but needs editing, duplicate it first:

# frozen_string_literal: true

path = "users/".dup
path << "123"

Common pattern: use interpolation or + for new strings

Instead of mutating an existing literal:

full_name = first_name + " " + last_name

or:

full_name = "#{first_name} #{last_name}"

These create new strings rather than changing a frozen literal.

Validation and error messages

Developers often store fixed messages safely:

# frozen_string_literal: true

ERROR_MESSAGE = "Invalid API token"

Guarding against accidental mutation

Without frozen literals, helper methods can accidentally change a shared string literal and create confusing bugs. Enabling frozen literals makes such mistakes fail fast with FrozenError, which is easier to debug.

Common Mistakes

Mistake 1: Thinking all strings become frozen

This comment does not freeze every string in Ruby.

# frozen_string_literal: true

a = "hello"
b = String.new("hello")

puts a.frozen? # true
puts b.frozen? # false

Mistake 2: Trying to append to a literal directly

Broken code:

# frozen_string_literal: true

text = "abc"
text << "def"

This raises FrozenError.

Fix it like this:

# frozen_string_literal: true

text = "abc".dup
text << "def"

Mistake 3: Confusing + with <<

+ creates a new string. << mutates the existing string.

# frozen_string_literal: true

a = "hi"
b = a + " there"   # works
#a << " there"     # would fail

Mistake 4: Putting the comment in the wrong place

The magic comment should be near the top of the file.

Typical form:

#!/usr/bin/env ruby
# frozen_string_literal: true

If it is buried deep in the file, Ruby may not treat it as a magic comment.

Mistake 5: Assuming older code will always work unchanged

Older Ruby code sometimes mutates literals freely:

message = "Error: "
message << details

With frozen string literals enabled, this pattern may break. Use dup, interpolation, or build a new string instead.

Mistake 6: Forgetting file-level scope

Adding the comment to one file does not affect another file. Each file controls its own string literal behavior.

Comparisons

ConceptWhat it doesMutable?Scope
Normal string literal"hello" creates a regular string literalUsually yesCurrent object
# frozen_string_literal: trueFreezes string literals in the fileNo for literals in that fileCurrent file
"hello".freezeFreezes one specific string objectNoThat object only
String.new("hello")Creates a new string objectYes by defaultThat object only
+ with stringsCreates a new combined stringResult is usually mutable unless frozen laterExpression result
<< with stringsModifies the existing stringRequires mutable stringExisting object

+ vs <<

# frozen_string_literal: true

a = "hi"

b = a + " there"  # works
#a << " there"    # FrozenError
  • Use + when you want a new string
  • Use << only when the receiver is mutable and you intentionally want to change it

Magic comment vs .freeze

Use # frozen_string_literal: true when you want file-wide behavior.

Use .freeze when you want to freeze a specific object only.

Take Quiz

Cheat Sheet

# At the top of a file
# frozen_string_literal: true

What it means

  • Freezes string literals in that Ruby file
  • Helps prevent accidental mutation
  • Can improve memory/performance in some cases
  • Applies per file, not globally

Affected

# frozen_string_literal: true
s = "hello"
s.frozen? # => true

Not automatically affected

# frozen_string_literal: true
s = String.new("hello")
s.frozen? # => false

Safe operations on frozen strings

name = "Ruby"
name.upcase      # returns a new string
name + "!"      # returns a new string
"#{name} rocks" # creates a new string

Operations that fail

name = "Ruby"
name << "!"     # FrozenError
name.gsub!("R", "B") # FrozenError

How to modify anyway

name = "Ruby".dup
name << "!"

Remember

  • + creates a new string
  • << mutates existing string
  • magic comment affects literals in one file only
  • put the comment near the top of the file

FAQ

What does # frozen_string_literal: true do in Ruby?

It tells Ruby to freeze string literals in that file, making them immutable.

Does it freeze every string in my program?

No. It only affects string literals in the current file.

Why do Bundler or generated files include it?

Generated files often contain many fixed strings that should not be modified, so freezing them is safe and useful.

What error happens if I modify a frozen string?

Ruby raises FrozenError.

Can I still combine frozen strings?

Yes. Operations like + and interpolation create new strings, so they still work.

How do I make a frozen string editable?

Use dup to create a mutable copy:

editable = "hello".dup

Is this the same as calling .freeze everywhere?

It has a similar effect for literals, but the magic comment applies automatically to all string literals in that file.

Should I always use # frozen_string_literal: true?

It is common and helpful in many Ruby codebases, but you should understand that code mutating literals may need to be updated first.

Take Quiz

Mini Project

Description

Create a small Ruby script that builds status messages for a command-line app while using # frozen_string_literal: true. This project demonstrates the difference between reading fixed string literals and trying to mutate them. It also shows how to correctly create editable copies when needed.

Goal

Build a Ruby script that uses frozen string literals safely and produces formatted output without raising FrozenError.

Requirements

  • Add # frozen_string_literal: true at the top of the file.
  • Define at least two fixed string literals for labels or message prefixes.
  • Build a final message using safe string operations.
  • Create one mutable copy with dup and modify it.
  • Print the final results to the console.
Take Quiz

Keep learning

Related questions

View more