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.
: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.
"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 symbolsPython 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/internandto_sconvert between String and Symbol at data boundaries like JSON parsing.- Calling
to_symon unbounded, untrusted user input is a minor anti-pattern, historically tied to symbol garbage-collection concerns.
Practice what you learned
1. Why is comparing two symbols generally faster than comparing two equal-content strings?
2. What does `:name.frozen?` return in modern Ruby?
3. Which method converts a String to a Symbol?
4. Why are symbols favored over strings for hash keys in idiomatic Ruby?
5. What historical concern existed (pre-Ruby 2.2) around calling to_sym on unbounded user input?
Was this page helpful?
You May Also Like
Strings and String Methods
How Ruby represents text with the String class, including literal syntax, interpolation, mutability, encoding, and the most commonly used string methods.
Hashes in Ruby
How Ruby's Hash class stores key-value pairs, including modern literal syntax, symbol keys, default values, and the Enumerable methods it inherits.
Variables and Data Types
Learn how Ruby variables work, its dynamic typing model, and the core built-in data types: integers, floats, strings, symbols, booleans, and nil.
Method Arguments and Defaults
Learn how Ruby methods accept required, optional, splat, keyword, and block arguments, and how default values keep call sites flexible and expressive.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics