<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Hinokami Dev</title>
    <description>The latest articles on Forem by Hinokami Dev (@hinokami_dev).</description>
    <link>https://forem.com/hinokami_dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3593156%2F76f00b9e-1650-4a1a-acd8-7f9e26f1d15f.jpg</url>
      <title>Forem: Hinokami Dev</title>
      <link>https://forem.com/hinokami_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hinokami_dev"/>
    <language>en</language>
    <item>
      <title>Why Ruby Devs Keep Mixing Up Symbols and Frozen Strings; and How to Tell Them Apart</title>
      <dc:creator>Hinokami Dev</dc:creator>
      <pubDate>Wed, 05 Nov 2025 17:03:19 +0000</pubDate>
      <link>https://forem.com/hinokami_dev/why-ruby-devs-keep-mixing-up-symbols-and-frozen-strings-3id8</link>
      <guid>https://forem.com/hinokami_dev/why-ruby-devs-keep-mixing-up-symbols-and-frozen-strings-3id8</guid>
      <description>&lt;p&gt;Every time Ruby developers start talking about &lt;code&gt;# frozen_string_literal: true&lt;/code&gt;, someone inevitably brings up &lt;strong&gt;symbols&lt;/strong&gt;. It happens so often it’s almost a ritual. Many people assume symbols are just frozen strings wearing a different outfit, but that’s not actually true. Let’s unpack why this confusion exists and what really sets them apart.&lt;/p&gt;




&lt;h3&gt;
  
  
  What Symbols Really Are
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Symbol&lt;/strong&gt; in Ruby is an immutable &lt;em&gt;identifier&lt;/em&gt;.&lt;br&gt;
It’s stored once in Ruby’s &lt;strong&gt;symbol table&lt;/strong&gt;, and every reference to it points to the same object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ruby uses symbols internally for method names, variable names, and hash keys.&lt;br&gt;
They’re meant to represent &lt;strong&gt;identity&lt;/strong&gt;, not text.&lt;/p&gt;

&lt;p&gt;If you think of symbols as labels that are fast to compare and cheap to store, you’ll use them correctly.&lt;/p&gt;


&lt;h3&gt;
  
  
  What Frozen Strings Really Are
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;frozen string&lt;/strong&gt; is still a &lt;code&gt;String&lt;/code&gt; object, it just can’t be modified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ready"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"set"&lt;/span&gt;   &lt;span class="c1"&gt;# =&amp;gt; FrozenError: can't modify frozen String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Freezing prevents accidental mutation and can improve performance by reusing literal objects.&lt;/p&gt;

&lt;p&gt;However, whether two identical string literals point to the same object depends on whether &lt;strong&gt;frozen string literals are enabled&lt;/strong&gt;.&lt;br&gt;
Without the &lt;code&gt;# frozen_string_literal: true&lt;/code&gt; magic comment, each literal creates a new string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"ready"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"ready"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With frozen string literals enabled, Ruby automatically interns identical literals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;
&lt;span class="s2"&gt;"ready"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"ready"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This interning only applies to string literals known at parse time. Strings created dynamically and then frozen are still separate objects and are not globally interned like symbols.&lt;/p&gt;

&lt;p&gt;That means Ruby reuses the same string object only for literal values, not for dynamically generated ones.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why People Confuse Them
&lt;/h3&gt;

&lt;p&gt;Both symbols and frozen strings are immutable and can be reused in memory.&lt;br&gt;
That’s where the confusion starts, but it’s only surface-level.&lt;/p&gt;

&lt;p&gt;There are two key differences.&lt;/p&gt;
&lt;h4&gt;
  
  
  1. The semantic difference
&lt;/h4&gt;

&lt;p&gt;Symbols are meant to represent names or identifiers in your program such as method names, variable names, and event types.&lt;br&gt;
Strings, even when frozen, represent text content that is meant to be read, displayed, or stored.&lt;/p&gt;
&lt;h4&gt;
  
  
  2. The implementation difference
&lt;/h4&gt;

&lt;p&gt;Both symbols and frozen strings can be &lt;strong&gt;interned&lt;/strong&gt;, meaning Ruby keeps a single copy in memory for identical values.&lt;br&gt;
However, all symbols are always interned, while only some strings are (those made literal under &lt;code&gt;# frozen_string_literal: true&lt;/code&gt; or explicitly interned).&lt;/p&gt;

&lt;p&gt;This subtle difference means even if two strings have the same content, one could be interned and another not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;                 &lt;span class="c1"&gt;# =&amp;gt; true (same content)&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; false (different objects)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Symbols don’t have that split personality because there’s always exactly one &lt;code&gt;:foo&lt;/code&gt; object in memory.&lt;/p&gt;




&lt;h3&gt;
  
  
  A Note on Hashing and Performance
&lt;/h3&gt;

&lt;p&gt;Because a string can exist both as an interned and a non-interned version, Ruby’s &lt;code&gt;String#hash&lt;/code&gt; must compute the hash based on its &lt;strong&gt;content&lt;/strong&gt;, which takes O(n) time.&lt;br&gt;
A symbol, however, has a unique internal ID, so &lt;code&gt;Symbol#hash&lt;/code&gt; can be computed in constant time, O(1).&lt;/p&gt;

&lt;p&gt;This ensures that two strings with the same content, even if one is interned and the other is not, always produce the same hash value.&lt;/p&gt;

&lt;p&gt;That’s part of why symbols make efficient hash keys for things like configuration maps or method lookups.&lt;/p&gt;


&lt;h3&gt;
  
  
  Why It Matters
&lt;/h3&gt;

&lt;p&gt;They may look the same, but they’re not interchangeable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="ss"&gt;:status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"status"&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Freezing a string doesn’t make it a symbol, it just prevents mutation.&lt;br&gt;
If you need a key, event name, or label, use a symbol.&lt;br&gt;
If you’re handling user input or text data, use a string.&lt;/p&gt;

&lt;p&gt;Symbols are safe for internal identifiers.&lt;br&gt;
Strings are safe for external, unpredictable data.&lt;/p&gt;




&lt;h3&gt;
  
  
  Memory Notes
&lt;/h3&gt;

&lt;p&gt;Before Ruby 2.2, symbols were never garbage-collected, which made dynamic symbol creation dangerous.&lt;br&gt;
From Ruby 2.2 onward, runtime symbols are GC-safe, but the convention to avoid symbolizing user input still stands for clarity, safety, and backward compatibility.&lt;/p&gt;

&lt;p&gt;Frozen strings, on the other hand, are always garbage-collected normally.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Takeaway
&lt;/h3&gt;

&lt;p&gt;Symbols represent &lt;strong&gt;identity&lt;/strong&gt;.&lt;br&gt;
Frozen strings represent &lt;strong&gt;content&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Both are immutable and both can be reused in memory, but their &lt;strong&gt;intent&lt;/strong&gt; and &lt;strong&gt;behavior&lt;/strong&gt; are different.&lt;br&gt;
Ruby’s frozen string literal feature was about safer, faster strings, not about turning them into symbols.&lt;/p&gt;

&lt;p&gt;So next time someone says &lt;em&gt;“A frozen string is basically a symbol,”&lt;/em&gt; remember:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not every frozen thing is a symbol.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ruby</category>
      <category>immutability</category>
      <category>backend</category>
      <category>rails</category>
    </item>
  </channel>
</rss>
