JavaScript Symbols: the Most Misunderstood Feature of the Language?

Fernando Doglio
Bits and Pieces
Published in
6 min readAug 23, 2021

--

Image by Anemone123 from Pixabay

”What does the Symbol has that I don’t” asked the String to the developer, and he answered — “Well, it is one of a kind”

It’s a terrible line, I know, but stick with me for a second, would you? Symbols are one of those strange features of JavaScript that because no one really understands, they don’t use it.

But there is a reason why they’re there, don’t you think? Just like the with is there for a reason — whether we like it or not — , Symbols have a purpose. And we’re here to figure it out.

The basics: What are Symbols?

Long story short, Symbols are a fairly new primitive type that you now have access to, next to strings, numbers, booleans, undefined and null .

Let’s break that down a bit. Primitive values are the actual value assigned to a variable, so in the following code:

let a = "foo"

The primitive is “foo”, not a . The latter is a variable that’s been assigned a primitive value. Why am I making this distinction? Because you don’t normally use primitive values directly, instead you assigned them to variables to give them meaning. In other words, 2 by itself means nothing, but let double = 2; suddenly has meaning.

Another very interesting aspect around this, is that primitives in JavaScript are immutable. That means you can’t change the number 2 ever. Instead, you can reassign a variable to have another (immutable) primitive value.

Now, bringing it all into the scope of Symbols we can gather that:

  • Symbols are primitive values and can be assigned to variables.
  • Symbols are immutable, you can’t change them, no matter how much you try.

They also have one extra quality: they’re unique across the execution of your script.

The first two characteristics are classic for all values, but the second one isn’t, and that is what makes them so special.

The uniqueness of Symbols

Let’s do some tests:

That result right there is what makes Symbols so special: every time you use them, you’re creating a new one. Even if you give them the same name!

For debugging purposes, you can provide a single attribute to the Symbol constructor, a string. That value is of no use other than debugging.

So what’s the use of a value that you can’t really match ever again? I mean, it’s not like I can do:

let obj = {}
obj[Symbol()] = 10;
console.log(obj[Symbol()]);

That wouldn’t work. Granted, you can save the Symbol into a variable, and then use that instead to reference the unique property. But you’d have to keep track of every symbol inside a new variable. It kind of looks like a watered-down version of a String, to be honest, doesn’t it?

That’s only until you learn about the global symbol cache!

Instead of directly creating symbols by calling the Symbol function, you can use the Symbol.for method.

The first call to for will create a new symbol, while the second one will only retrieve it. And here is where our little friends start deviating from strings. You see, while the string "hello" will always be equal to "hello" for obvious reasons, they’re two different strings. So when we do:

We’re creating:

  • A primitive value of type number (10)
  • 2 primitive values of type string (the two “prop” strings)

How relevant is that? Let’s take a look at some use cases for symbols!

When would you want to use symbols and why?

Look at the last example, how expensive — resource-wise — can that be? How much memory would you say we’ve consumed by creating one extra string? It’s so little that I don’t even want to bother making that calculation.

Especially if you’re working on a web dev environment.

However, if you’re running into some edge cases, like potentially doing some data processing that generates big in-memory dictionaries, or perhaps you’re using JavaScript on edge devices with limited memory, then symbols can be a great way of keeping memory utilization in check.

Adding “invisible” methods

Another interesting use case for symbols comes from the fact that they’re so unique. They can be used to provide custom, unique “hooks” to objects. Like the toString hook that you can add on a custom object and that will get called by console.log when serializing it.

The key here is that by using symbols, you’re avoiding a potential name collision with whatever name the user gives to their methods. Unless they specifically use your symbol, there won’t be a problem.

Look at that code, what do you think the output will be?

It’s going to be "damn son! because that’s the method you defined using the unique symbol. The other one is essentially treated as a string, so they’re not the same and you’re not overwriting it.

Note that for the sake of simplicity, I’ve added a property to Object and this is not something you’d want to do. Instead create a custom class to properly control who inherits this custom hook.

Metadata for your classes

The last interesting use case for symbols is adding properties to your classes and objects that are really not part of their “shape”.

Let me explain: the “shape” of an object is given by its properties. And the only way to add data to the object, without affecting its shape (its original set of properties) is by adding them as symbols. This is because these types of properties won’t show up as part of for..in loops, or as a result of calling Object.keys

You can see these properties as living inside a higher abstraction layer than your regular properties.

Notice how I’m adding two pieces of metadata to my object: the last property accessed through the getters I defined, and the date & time of when that happened. This is metadata because it’s information not relevant to the business needs of my object, but instead, it’s information about my object.

After the class definition, I show you a few things:

  • Symbol properties are public. This is obvious right now, because private properties aren’t yet part of the working ES version. We’ll have to see how this fits into that definition once they’re done testing it.
  • Iterating over all properties of the object in lines 35–37 does not show my custom, symbol-props. This comes in handy if you’re trying to access debug data inside your classes, because you know it won’t affect the regular behavior of your code.

As you can see, symbols are probably not the richest feature of JavaScript, but they’re also not the worst. There is a very good reason why Symbols are part of the specs and now that you know some interesting use cases for them, consider taking the time to use them and take advantage of them.

Have you used symbols in any other way in your code? Share your use cases in the comments and let’s show everyone the power of this construct!

Build anything from independent components

Say goodbye to monolithic applications and leave behind you the tears of their development.

The future is components; modular software that is faster, more scalable, and simpler to build together. OSS Tools like Bit offer a great developer experience for building independent components and composing applications. Many teams start by building their Design Systems or Micro Frontends, through shared components. Give it a try →

--

--

I write about technology, freelancing and more. Check out my FREE newsletter if you’re into Software Development: https://fernandodoglio.substack.com/