Ruby Symbols Explained

Q: What’s the difference between :foo and "foo"?

A: :foo is a symbol, and "foo" is a string.

Q: What’s a symbol?

A: A symbol is like a “digested” string.

"foo".to_sym # => :foo

Symbols have two nice properties compared to strings which can save you memory and CPU time:

Memory

For every unique string value, there is a unique symbol object. Three strings with the same value can use up to three times the memory of three symbols with the same value. This is because those three symbols are actually the same object.

p "blah".object_id #=> -605600196
p "blah".object_id #=> -605618816
p "blah".object_id #=> -605637168

p :blah.object_id #=> 4071694
p :blah.object_id #=> 4071694
p :blah.object_id #=> 4071694

p "blah".to_sym.object_id #=> 4071694
p "blah".to_sym.object_id #=> 4071694
p "blah".to_sym.object_id #=> 4071694

Symbol objects are created on-demand behind the scenes for new unique string values. Once a symbol exists for a particular string value, it will continue to be used for that value rather than creating more symbols for it.

Of course, this has a disadvantage: Ruby keeps symbols around forever (well, until the end of your program anyway), in case they are needed again.

That’s fine if the number of unique symbols is relatively small and you expect to keep reusing them. However, if you have an unlimited number of string values, and/or you only use each one briefly (for example, lines arriving on an input stream), symbols are not the tool for you. You’ll just use up all your memory.

CPU Time

Testing two symbol values for equality (or non-equality) is faster than testing two string values for equality, because Ruby only needs to do a single test. Checking two strings for equality is more complicated; every individual character in the string has to be checked until a difference is found.

As noted above, each unique string value has an associated symbol. This means that checking whether two symbols have the same string value or not is as simple as checking whether they are the same object or not.

One comparison:

:Worcestershire == :Worcestershire

Easy peasy. They’re the same object, so they’re equal.

Sixteen comparisons:

"Worcestershire" == "Worcestershire"

With strings, Ruby has to dig into the objects to check their contents. Since in this case they’re different string objects with the same length, it’s got to check all fourteen characters in each string to make sure that they really are equal.

However, what symbols don’t give you is lexical comparison. You can do "foo" > "bar", but not :foo > :bar. They’re digested. Their original string values are a memory which can be resurrected only via Symbol#to_s.

The Right Tool for the Job

If you have a modest number of unique string values that you want to use over and over, and will mostly be using them as-is (i.e. not concatenating them, uppercasing them, etc….), then symbols are the right tool for you.

Method names (such as those given to Module#attr_reader, or Object#send) are a good example of this; in fact, Ruby uses symbols internally to remember method, variable, and constant names.

Otherwise, stick with strings. Strings are not that much slower than symbols, and many times the extra limitations of symbols aren’t worth it.