Skip to Content
Course content

54: Symbols and Their Use Cases

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I want to show you a problem I ran into recently while building a plugin system for a project. I had this user object coming from an API, and I needed to attach some internal metadata to it—basically some "private" tracking info—that shouldn't interfere with the actual user data. My first instinct was to just use a string key with an underscore, which is a common convention for "private" properties.

const user = {
  name: 'Alex',
  email: 'alex@example.com'
};

// Adding some internal tracking
user._internalId = 'track_9921';

console.log(user); 
// { name: 'Alex', email: 'alex@example.com', _internalId: 'track_9921' }

The Collision Headache

This works fine until you realize that in a large codebase, you aren't the only one writing code. Imagine another developer—or a third-party library you installed—also decides to use _internalId to track something completely different. Suddenly, they overwrite your data, or you overwrite theirs, and you're spending four hours debugging a "ghost in the machine" because two different parts of the app are fighting over the same property name.

I wondered if there was a way to create a key that is guaranteed to be unique, regardless of what string name I give it. That's where Symbol comes in. Let's try replacing that string with a Symbol.

const internalId = Symbol('internalId');

user[internalId] = 'track_9921';

console.log(user[internalId]); // 'track_9921'

Now, you might look at Symbol('internalId') and think, "Wait, isn't that just a string in disguise?" I thought the same thing. So, I tried to create another Symbol with the exact same description to see if they'd match.

const id1 = Symbol('internalId');
const id2 = Symbol('internalId');

console.log(id1 === id2); // false

This is the "aha!" moment. Even though they have the same label, they are entirely distinct entities. If I use id1 as a key in my plugin and another dev uses id2 in theirs, we can both attach data to the same user object and never, ever collide. The label 'internalId' is just for us humans to make debugging easier; JS ignores it during comparisons.

Vanishing Acts and Object Keys

While I was playing with this, I noticed something interesting. Usually, when I'm logging objects or iterating through them to build a UI list, I use Object.keys() or a for...in loop. I decided to see if my new Symbol property would show up there.

const user = { name: 'Alex' };
const secretKey = Symbol('secret');
user[secretKey] = 'shhh!';

console.log(Object.keys(user)); // ['name']
console.log(JSON.stringify(user)); // {"name":"Alex"}

for (let key in user) {
  console.log(key); // only logs 'name'
}

The Symbol is effectively invisible to standard iteration. It's not "private" in the sense of a hard security wall (don't mistake this for true encapsulation), but it is non-enumerable. This is incredibly useful. It means I can attach metadata to an object without breaking existing logic that expects the object to have a specific set of keys, and it won't accidentally get sent back to a server during a JSON.stringify() call.

Getting the Keys Back

Of course, if it's invisible, I can't debug it. I needed a way to see what Symbols were actually attached to the object. I dug into the documentation and found a specific method for this.

const symbols = Object.getOwnPropertySymbols(user);
console.log(symbols); // [ Symbol(secret) ]
console.log(user[symbols[0]]); // 'shhh!'

So, here is the mental model I use now: Use strings for data that defines the object and should be public. Use Symbols for "sidecar" data—things the object needs to function or be tracked, but which shouldn't interfere with the object's primary identity or be exposed during routine iterations.




📋 Practical Task

Exercise: Implementing a Collision-Proof Metadata Store for User Profiles

You are building a system where multiple different "Analytics Plugins" attach their own unique tracking IDs to a shared user object. If you use strings, the plugins will overwrite each other.

Your Task:

  • Create a user object with username and email.
  • Create two distinct Symbols: one called pluginAId and one called pluginBId.
  • Attach a unique ID value to the user object using both Symbols.
  • Verify that:
    1. Object.keys(user) does NOT contain the Symbols.
    2. You can still retrieve both unique IDs from the user object using the Symbol variables.
    3. JSON.stringify(user) only shows the username and email.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.