100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Ruby

Symbols Explained

What Ruby symbols are, how they differ from strings under the hood, and why they're the idiomatic choice for hash keys, method names, and identifiers.

Strings, Arrays & HashesBeginner8 min readJul 9, 2026
Analogies

Symbols Explained

A Symbol is a lightweight, immutable identifier written with a leading colon, like :name or :active. Symbols look similar to strings — both represent sequences of characters — but they behave completely differently at the object level. Every occurrence of a given symbol literal, anywhere in a program, refers to the exact same object in memory, whereas every string literal creates a new object (unless frozen). This makes symbols ideal for values that represent a fixed, known set of identifiers — hash keys, method names, enum-like states — rather than arbitrary user-facing text.

🏏

Cricket analogy: A Symbol like :active is like a player's fixed jersey number 18 -- every scorecard reference to '18' points to the same identity, whereas writing out 'Virat Kohli' as text each time creates a fresh string with no guaranteed shared identity.

Symbols vs. strings

Because symbols are interned (deduplicated by the Ruby VM), comparing two symbols for equality is an extremely fast object-identity check, whereas comparing two strings requires a character-by-character comparison. Symbols are also always frozen — there's no Symbol#upcase! that mutates in place, because a symbol simply doesn't support in-place modification the way a String does. This combination of speed and immutability is why symbols are the default choice for hash keys and method/keyword-argument names throughout Ruby's standard library.

🏏

Cricket analogy: Comparing two symbols is like checking if two players share the exact same jersey number -- an instant glance -- while comparing two strings is like reading out full player names letter by letter to confirm a match; and a symbol can't be 'renamed' mid-match the way a nickname string could be edited.

ruby
:name.object_id == :name.object_id   # => true, same object every time
"name".object_id == "name".object_id # => false, distinct objects

:name.frozen?         # => true, symbols are always frozen
:name.upcase           # => :NAME (returns a new symbol, no mutation possible)

Where symbols appear, and converting to/from strings

Symbols appear throughout idiomatic Ruby: as hash keys ({ status: :active }), as fixed identifiers (attr_accessor :name, send(:upcase)), and as keyword argument names. to_sym (aliased intern) converts a String to a Symbol, and to_s converts a Symbol back to a String — a common conversion at boundaries where data enters as text, such as reading form input or parsing JSON, before being normalized to symbols for internal use.

🏏

Cricket analogy: Using status: :active as a hash key is like labeling a player's file with a fixed status code rather than free text; to_sym converting a scorer's typed-in string 'active' into the fixed symbol is like normalizing a handwritten note into the official status code once it's entered into the system.

ruby
"active".to_sym   # => :active
:active.to_s       # => "active"

class Order
  STATUSES = %i[pending shipped delivered].freeze
  attr_accessor :status
  def initialize; @status = :pending; end
end

JSON.parse('{"role":"admin"}', symbolize_names: true)
# => {role: "admin"} -- keys become symbols

Python has no direct equivalent of Ruby's Symbol; the closest analogues are interned strings (via sys.intern) or Enum members, but neither is as pervasively idiomatic as symbols are in Ruby, where they're the default hash-key convention rather than an opt-in optimization.

Before Ruby 2.2, symbols created from untrusted user input (e.g. params[:field].to_sym) were never garbage collected, creating a memory-leak / denial-of-service risk if an attacker could generate arbitrarily many unique symbols. Modern Ruby (2.2+) garbage-collects most symbols, but it's still best practice to avoid calling to_sym on unbounded user input.

  • A Symbol is an immutable, interned identifier (:name) — every reference to the same symbol literal points to one shared object.
  • Symbol equality is a fast object-identity check, while String equality requires character-by-character comparison.
  • Symbols are always frozen; there is no way to mutate a symbol in place.
  • Idiomatic Ruby uses symbols for hash keys, method names, and keyword arguments, reserving Strings for arbitrary text data.
  • to_sym/intern and to_s convert between String and Symbol at data boundaries like JSON parsing.
  • Calling to_sym on unbounded, untrusted user input is a minor anti-pattern, historically tied to symbol garbage-collection concerns.

Practice what you learned

Was this page helpful?

Topics covered

#Ruby#RubyStudyNotes#Programming#SymbolsExplained#Symbols#Explained#Strings#Where#StudyNotes#SkillVeris