Skip to main content

Symbol

Definition

A Symbol is a unique and immutable primitive value that may be used as the key of an object property. Symbols are guaranteed to be unique—even if you create two symbols with the same description, they are different values.

> typeof Symbol()
"symbol"

Using symbols

Symbols are created using the Symbol() function:

// Basic symbol
const sym1 = Symbol()
const sym2 = Symbol()

sym1 === sym2 // false (always unique)

// Symbol with description
const sym3 = Symbol("description")
const sym4 = Symbol("description")

sym3 === sym4 // false (still unique, description is just metadata)

Symbol.for() and Symbol.keyFor()

Symbol.for()

Creates a symbol in the global symbol registry. If a symbol with the given key already exists, it returns that symbol.

const sym1 = Symbol.for("key")
const sym2 = Symbol.for("key")

sym1 === sym2 // true (same symbol from registry)

Symbol.keyFor()

Retrieves the key for a symbol from the global symbol registry.

const sym = Symbol.for("key")
Symbol.keyFor(sym) // "key"

const localSym = Symbol("key")
Symbol.keyFor(localSym) // undefined (not in registry)

Using symbols as object keys

Symbols can be used as unique property keys:

const sym = Symbol("id")
const obj = {
name: "Alice",
[sym]: 12345
}

console.log(obj[sym]) // 12345
console.log(obj.name) // "Alice"

Symbol properties are hidden

Symbol properties are not enumerable and won't appear in for...in loops or Object.keys():

const sym = Symbol("id")
const obj = {
name: "Alice",
[sym]: 12345
}

for (let key in obj) {
console.log(key) // Only "name"
}

Object.keys(obj) // ["name"]
Object.getOwnPropertySymbols(obj) // [Symbol(id)]

Operations on symbols

Well-known symbols

JavaScript has several built-in symbols that control language behavior:

JavaScript has several built-in symbols that control language behavior:

Symbol.iterator

Makes an object iterable:

const obj = {
*[Symbol.iterator]() {
yield 1
yield 2
yield 3
}
}

[...obj] // [1, 2, 3]

Symbol.toStringTag

Customizes the default string description of an object:

class MyClass {
get [Symbol.toStringTag]() {
return "MyClass"
}
}

String(new MyClass()) // "[object MyClass]"

Symbol.toPrimitive

Converts an object to a primitive value:

const obj = {
[Symbol.toPrimitive](hint) {
if (hint === "number") return 42
if (hint === "string") return "hello"
return "default"
}
}

+obj // 42
String(obj) // "hello"

Symbol.hasInstance

Customizes the behavior of the instanceof operator:

class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance)
}
}

[] instanceof MyArray // true

Symbol.isConcatSpreadable

Controls whether an object should be flattened when used with Array.prototype.concat():

const arr = [1, 2]
arr[Symbol.isConcatSpreadable] = false
[].concat(arr) // [[1, 2]] instead of [1, 2]

Symbol.match, Symbol.replace, Symbol.search, Symbol.split

Customize string matching behavior:

class MyMatcher {
constructor(value) {
this.value = value
}
[Symbol.match](string) {
return string.indexOf(this.value)
}
}

"hello".match(new MyMatcher("ll")) // 2

Common Use Cases

Private Properties

Symbols can be used to create "private" properties (though not truly private):

const _private = Symbol("private")

class MyClass {
constructor() {
this[_private] = "secret"
}

getPrivate() {
return this[_private]
}
}

Avoiding Property Name Collisions

Symbols ensure unique property names:

const MY_FEATURE = Symbol("myFeature")

// Multiple libraries can use the same symbol key
library1[MY_FEATURE] = "value1"
library2[MY_FEATURE] = "value2" // No collision

Metadata

Symbols can store metadata on objects:

const metadata = Symbol("metadata")

function addMetadata(obj, data) {
obj[metadata] = data
}

function getMetadata(obj) {
return obj[metadata]
}

Symbol methods

Symbol.prototype.description

Returns the optional description of a symbol:

const sym = Symbol("my description")
sym.description // "my description"

const sym2 = Symbol()
sym2.description // undefined

Best Practices

  1. Use for unique keys: When you need guaranteed unique property names.

  2. Use well-known symbols: To customize language behavior (iterators, toString, etc.).

  3. Don't use for true privacy: Symbols are discoverable via Object.getOwnPropertySymbols().

  4. Use descriptions: Always provide meaningful descriptions for debugging:

// Good
const userId = Symbol("userId")

// Less helpful
const userId = Symbol()