JavaScript ES6 Features: Set, Map, WeakSet, WeakMap, Symbols, Iterators, and Generators

ECMAScript 2015, also known as ES6, introduced several powerful features that make JavaScript more flexible and easier to work with. These features include new data structures, unique identifiers, and tools for controlling how values are processed.

JavaScript provides Set and Map to store collections of values and key-value pairs. WeakSet and WeakMap provide specialized collections that hold weak references to objects. Symbols create unique primitive values, while iterators and generators provide ways to process sequences of data.

In this tutorial, you will learn how these ES6 features work, understand their differences, and explore practical examples you can use in your JavaScript projects.

What Is a Set in JavaScript?

A Set is a built-in JavaScript object that stores a collection of unique values. Unlike an array, a Set does not store duplicate values.

You can store strings, numbers, objects, and other types of values in a Set.

Creating a Set

Use the Set constructor to create a new Set.

const numbers = new Set([10, 20, 30, 20, 10]);

console.log(numbers);

Output:

Set(3) { 10, 20, 30 }

The duplicate values 10 and 20 are stored only once.

Adding Values to a Set

Use the add() method to insert a value into a Set.

const fruits = new Set();

fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");

console.log(fruits);

Output:

Set(3) { 'Apple', 'Banana', 'Orange' }

Calling add() with a value that already exists does not create a duplicate.

const fruits = new Set(["Apple", "Banana"]);

fruits.add("Apple");

console.log(fruits.size);

Output:

2

The size property returns the number of unique values in the Set.

Checking Whether a Value Exists

Use the has() method to check whether a value is present.

const languages = new Set(["JavaScript", "Python", "Java"]);

console.log(languages.has("JavaScript"));
console.log(languages.has("PHP"));

Output:

true
false

The has() method returns true if the value exists and false otherwise.

Removing Values From a Set

Use delete() to remove a particular value.

const colors = new Set(["Red", "Green", "Blue"]);

colors.delete("Green");

console.log(colors);

Output:

Set(2) { 'Red', 'Blue' }

To remove all values, use clear().

const numbers = new Set([1, 2, 3]);

numbers.clear();

console.log(numbers.size);

Output:

0

Looping Through a Set

You can use a for...of loop to iterate over Set values.

const courses = new Set(["HTML", "CSS", "JavaScript"]);

for (const course of courses) {
  console.log(course);
}

Output:

HTML
CSS
JavaScript

Sets preserve the insertion order of their values.

When Should You Use a Set?

Use a Set when you need to:

  • Remove duplicate values from an array.
  • Store unique identifiers.
  • Check whether a value exists in a collection.
  • Maintain a collection without manually checking for duplicates.

For example, you can remove duplicate values from an array like this:

const numbers = [1, 2, 2, 3, 3, 4];

const uniqueNumbers = [...new Set(numbers)];

console.log(uniqueNumbers);

Output:

[1, 2, 3, 4]

The spread operator converts the Set back into an array. You can learn more in the JavaScript Spread Operator tutorial.

What Is a Map in JavaScript?

A Map is a built-in collection that stores data as key-value pairs. Each key in a Map is unique, and each key is associated with a value.

Unlike a regular JavaScript object, a Map allows keys of different types, including strings, numbers, objects, and functions.

Creating a Map

Use the Map constructor to create a Map.

const student = new Map();

student.set("name", "Karim");
student.set("age", 22);
student.set("course", "JavaScript");

console.log(student);

Output:

Map(3) {
  'name' => 'Karim',
  'age' => 22,
  'course' => 'JavaScript'
}

The set() method adds a key-value pair or updates the value associated with an existing key.

Getting Values From a Map

Use the get() method to retrieve a value using its key.

const student = new Map();

student.set("name", "Karim");
student.set("age", 22);

console.log(student.get("name"));
console.log(student.get("age"));

Output:

Karim
22

If the key does not exist, get() returns undefined.

Checking Whether a Key Exists

Use has() to check whether a key exists in a Map.

const user = new Map();

user.set("username", "karim123");

console.log(user.has("username"));
console.log(user.has("email"));

Output:

true
false

Removing Entries From a Map

Use delete() to remove an entry by its key.

const prices = new Map();

prices.set("Laptop", 50000);
prices.set("Mouse", 800);

prices.delete("Mouse");

console.log(prices.size);

Output:

1

Use clear() to remove all entries.

prices.clear();

console.log(prices.size);

Output:

0

Using Objects as Map Keys

One important feature of Map is that an object can be used as a key.

const user = { id: 101 };

const userDetails = new Map();

userDetails.set(user, "Administrator");

console.log(userDetails.get(user));

Output:

Administrator

Object keys are compared by identity. A different object containing the same properties is not automatically treated as the same key.

console.log(userDetails.get({ id: 101 }));

Output:

undefined

Although the new object has the same property, it is a different object in memory.

Looping Through a Map

You can use for...of to loop through Map entries.

const scores = new Map([
  ["Karim", 90],
  ["Ayesha", 85],
  ["Rahul", 88]
]);

for (const [name, score] of scores) {
  console.log(`${name}: ${score}`);
}

Output:

Karim: 90
Ayesha: 85
Rahul: 88

Map also provides keys(), values(), and entries() methods for accessing its keys, values, and entries.

When Should You Use a Map?

Use a Map when you need to:

  • Store related key-value pairs.
  • Use objects or other non-string values as keys.
  • Preserve the insertion order of entries.
  • Frequently add, update, retrieve, or remove entries.
  • Track information such as user scores, product prices, or application settings.

What Is a WeakSet in JavaScript?

A WeakSet is a collection that stores objects and symbols that are eligible for weak collection under JavaScript’s rules. Unlike a regular Set, it cannot store primitive values such as strings and numbers.

WeakSets hold weak references to their members. This means membership alone does not prevent an otherwise unreachable object from being garbage-collected.

WeakSets are useful when you want to track objects without keeping them alive solely because they are stored in the collection.

Creating a WeakSet

Use the WeakSet constructor.

const userOne = { name: "Karim" };
const userTwo = { name: "Ayesha" };

const users = new WeakSet([userOne, userTwo]);

console.log(users.has(userOne));
console.log(users.has(userTwo));

Output:

true
true

The WeakSet tracks these object references.

Adding Objects to a WeakSet

Use add() to insert an object.

const activeUsers = new WeakSet();

const user = { name: "Karim" };

activeUsers.add(user);

console.log(activeUsers.has(user));

Output:

true

Attempting to add a primitive value such as a string or number throws a TypeError.

Removing Objects From a WeakSet

Use delete() to remove an object reference.

const processedItems = new WeakSet();

const item = { id: 1 };

processedItems.add(item);
console.log(processedItems.has(item));

processedItems.delete(item);
console.log(processedItems.has(item));

Output:

true
false

Important WeakSet Limitations

WeakSet has a limited API. It supports methods such as add(), has(), and delete(), but it does not have:

  • A size property.
  • A clear() method.
  • Methods to retrieve all its members.
  • Standard iteration using for...of.

Because its members may be garbage-collected, JavaScript does not provide a normal enumeration API for WeakSet.

Practical Example: Tracking Processed Objects

Suppose an application processes each object only once.

const processed = new WeakSet();

function processUser(user) {
  if (processed.has(user)) {
    console.log("User already processed.");
    return;
  }

  processed.add(user);

  console.log(`Processing ${user.name}`);
}

const user = { name: "Karim" };

processUser(user);
processUser(user);

Output:

Processing Karim
User already processed.

The same object reference is recognized the second time.

A WeakSet is useful here because tracking the object does not, by itself, keep it alive indefinitely. However, WeakSet is not a replacement for permanent records or a persistent database.

What Is a WeakMap in JavaScript?

A WeakMap stores key-value pairs, similar to a regular Map, but its keys must be objects or non-registered symbols that are eligible for weak collection.

WeakMap holds its keys weakly. If a key object becomes otherwise unreachable, the WeakMap entry can be removed through garbage collection.

Unlike Map, WeakMap cannot be enumerated, and it does not provide a size property.

Creating a WeakMap

const user = { name: "Karim" };

const userData = new WeakMap();

userData.set(user, {
  role: "Developer",
  active: true
});

console.log(userData.get(user));

Output:

{ role: 'Developer', active: true }

The object user is the key, and its associated information is the value.

Getting Values From a WeakMap

Use get() to retrieve the value associated with an object key.

const product = { id: 101 };

const productData = new WeakMap();

productData.set(product, "Laptop");

console.log(productData.get(product));
console.log(productData.get({ id: 101 }));

Output:

Laptop
undefined

The second lookup returns undefined because it uses a different object reference.

Checking and Deleting WeakMap Entries

Use has() to check whether an entry exists and delete() to remove it.

const account = { id: 501 };

const accountData = new WeakMap();

accountData.set(account, "Active");

console.log(accountData.has(account));

accountData.delete(account);

console.log(accountData.has(account));

Output:

true
false

Practical Example: Storing Private-Like Object Data

A WeakMap can associate additional data with an object without adding that data directly to the object itself.

const privateData = new WeakMap();

function createUser(name) {
  const user = { name };

  privateData.set(user, {
    accessLevel: "admin"
  });

  return user;
}

const user = createUser("Karim");

console.log(user.name);
console.log(privateData.get(user).accessLevel);

Output:

Karim
admin

The accessLevel property is not directly stored on the returned user object.

This technique can help manage internal metadata, but it is not a complete security boundary. Code that can access privateData can retrieve its associated values.

Set vs Map vs WeakSet vs WeakMap

These four collections serve different purposes.

FeatureSetMapWeakSetWeakMap
StoresUnique valuesKey-value pairsObject references and eligible symbolsKey-value pairs
Duplicate entriesDuplicate values are ignoredKeys are uniqueDuplicate members are ignoredKeys are unique
Primitive valuesAllowedAllowed as keysNot allowedNot allowed as keys
Weak referencesNoNoYesYes, for keys
IterableYesYesNoNo
size propertyYesYesNoNo
Typical useUnique valuesGeneral key-value dataTracking objectsAssociating data with object keys

Choose a regular Set or Map when you need to iterate over entries or count them. Choose WeakSet or WeakMap when weak object references fit the use case and you do not need enumeration.

What Are Symbols in JavaScript?

A Symbol is a primitive data type introduced in ES6. Every call to Symbol() creates a unique symbol value.

Symbols are often used as property keys to avoid accidental naming conflicts between different parts of a program.

Creating a Symbol

const firstSymbol = Symbol();
const secondSymbol = Symbol();

console.log(firstSymbol === secondSymbol);

Output:

false

Even though both symbols were created without descriptions, they are different values.

You can provide a description to make a symbol easier to identify when debugging.

const userId = Symbol("userId");

console.log(userId.description);

Output:

userId

The description is informational. It does not make the symbol unique by itself; the symbol value is already unique.

Using Symbols as Object Properties

Symbols can be used as property keys.

const userId = Symbol("id");

const user = {
  name: "Karim",
  [userId]: 101
};

console.log(user.name);
console.log(user[userId]);

Output:

Karim
101

The square brackets allow the symbol value to be used as a computed property key.

For more information about computed properties, see JavaScript Enhanced Object Literals.

Avoiding Property Name Conflicts

Imagine two modules add an id property to the same object. One module uses the ID for a user, while another uses it for a transaction. A regular string property can create a naming conflict.

Symbols can help avoid this problem.

const userId = Symbol("id");
const transactionId = Symbol("id");

const record = {
  [userId]: 101,
  [transactionId]: 9001
};

console.log(record[userId]);
console.log(record[transactionId]);

Output:

101
9001

Although both symbols have the description "id", they are distinct property keys.

The Global Symbol Registry

JavaScript also provides Symbol.for() to retrieve or create a symbol in a global symbol registry.

const first = Symbol.for("app.user");
const second = Symbol.for("app.user");

console.log(first === second);

Output:

true

Unlike Symbol(), repeated calls to Symbol.for() with the same registry key return the same registered symbol.

Use this feature when separate parts of an application need to share a symbol by a known key.

Symbols Are Not Automatically Private

Symbol-keyed properties are not included in ordinary Object.keys() results or JSON serialization. However, they are not fully hidden or secure.

You can discover symbol properties using Object.getOwnPropertySymbols() or Reflect.ownKeys().

const secretKey = Symbol("secret");

const data = {
  name: "Example",
  [secretKey]: 123
};

console.log(Object.keys(data));
console.log(Object.getOwnPropertySymbols(data).length);

Output:

[ 'name' ]
1

Symbols are useful for unique property keys, but they should not be used to protect sensitive data.

What Are Iterators in JavaScript?

An iterator is an object that provides a sequence of values one at a time.

JavaScript iterators follow a standard protocol. An iterator object must have a next() method that returns an object containing:

  • value: the current value.
  • done: a Boolean indicating whether the sequence has finished.

When done is false, another value is available. When done is true, iteration has finished.

Understanding the Iterator Protocol

const iterator = {
  current: 1,

  next() {
    if (this.current <= 3) {
      return {
        value: this.current++,
        done: false
      };
    }

    return {
      value: undefined,
      done: true
    };
  }
};

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

Output:

{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }

Each call to next() returns the next result in the sequence.

Built-In Iterators

Arrays, strings, Maps, and Sets provide built-in iteration support.

For example:

const fruits = ["Apple", "Banana", "Orange"];

const iterator = fruits[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

Output:

{ value: 'Apple', done: false }
{ value: 'Banana', done: false }
{ value: 'Orange', done: false }
{ value: undefined, done: true }

The Symbol.iterator method returns an iterator for the array.

Using for...of With Iterables

The for...of loop uses the iterator protocol to process values.

const languages = ["JavaScript", "Python", "Java"];

for (const language of languages) {
  console.log(language);
}

Output:

JavaScript
Python
Java

An iterable is an object that provides a Symbol.iterator method. The iterator protocol defines how the values are produced.

This is why arrays, Sets, Maps, and strings can be used with for...of.

What Are Generators in JavaScript?

A generator is a special kind of function that can pause its execution and resume it later.

Generator functions use the function* syntax, and the yield keyword produces a value while pausing the function.

Calling a generator function does not immediately execute its entire body. Instead, it returns a generator object that provides a next() method.

Generators are useful for creating sequences, processing values gradually, and implementing custom iterators.

Creating a Generator Function

function* generateNumbers() {
  yield 1;
  yield 2;
  yield 3;
}

const numbers = generateNumbers();

console.log(numbers.next());
console.log(numbers.next());
console.log(numbers.next());
console.log(numbers.next());

Output:

{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }

Each call to next() resumes the generator until it reaches another yield or finishes.

After the generator finishes, subsequent calls to next() continue returning a completed result.

Using Generators With for...of

Generator objects are iterable, so you can use them with a for...of loop.

function* generateFruits() {
  yield "Apple";
  yield "Banana";
  yield "Orange";
}

for (const fruit of generateFruits()) {
  console.log(fruit);
}

Output:

Apple
Banana
Orange

The loop requests values from the generator until it finishes.

Generators With Loops

You can use a loop inside a generator to produce a sequence of values.

function* countUpTo(limit) {
  for (let number = 1; number <= limit; number++) {
    yield number;
  }
}

for (const number of countUpTo(5)) {
  console.log(number);
}

Output:

1
2
3
4
5

This generator produces values one at a time rather than building an array containing the entire sequence.

Passing Values Into a Generator

The next() method can accept a value that becomes the result of the suspended yield expression.

function* askName() {
  const name = yield "What is your name?";

  yield `Hello, ${name}!`;
}

const generator = askName();

console.log(generator.next());
console.log(generator.next("Karim"));

Output:

{ value: 'What is your name?', done: false }
{ value: 'Hello, Karim!', done: false }

The first next() starts the generator and pauses at yield. The second next("Karim") supplies the value that the suspended yield expression evaluates to.

Practical Example: Generate Unique IDs

A generator can produce sequential identifiers.

function* generateIds() {
  let id = 1;

  while (true) {
    yield id++;
  }
}

const ids = generateIds();

console.log(ids.next().value);
console.log(ids.next().value);
console.log(ids.next().value);

Output:

1
2
3

This generator is infinite, so it should be consumed carefully. Avoid using an unbounded generator directly in a loop that expects to finish.

Also, sequential IDs are not suitable as secure tokens or globally unique identifiers in distributed systems without additional design.

Iterators vs Generators

Iterators and generators are closely related, but they are not the same thing.

FeatureIteratorGenerator
What it isAn object following the iterator protocolA special function that creates a generator object
SyntaxUsually implemented with a next() methodUses function* and yield
State managementOften implemented manuallyExecution state is handled automatically
Produces valuesThrough next()Through yield and next()
Iterable supportDepends on whether it implements Symbol.iteratorGenerator objects are iterable

Generators provide a convenient way to create iterators without manually managing all the iterator state.

Frequently Asked Questions

What is the difference between Set and Map in JavaScript?

A Set stores unique values, while a Map stores key-value pairs with unique keys. Use Set to maintain a collection of unique items and Map to associate keys with values.

What is the difference between WeakSet and WeakMap?

A WeakSet tracks eligible object references and certain eligible symbols without strongly retaining them. A WeakMap associates values with eligible object or symbol keys. Neither collection supports normal enumeration.

Can a Set contain duplicate values?

No. A Set stores each value only once according to its value equality rules. Adding the same value again does not create a second entry.

Why are Symbols useful in JavaScript?

Symbols provide unique primitive values that can be used as property keys. They help prevent accidental property name conflicts between different parts of an application.

What is the difference between an iterator and an iterable?

An iterator provides a next() method that returns iteration results. An iterable is an object that provides a Symbol.iterator method to obtain an iterator.

What is the purpose of yield in JavaScript?

The yield keyword produces a value from a generator and pauses its execution until the generator resumes.

Can generators be infinite?

Yes. A generator can produce values indefinitely, for example, with a while (true) loop. Consume infinite generators with explicit limits or controlled iteration.

Are Set, Map, WeakSet, WeakMap, Symbols, Iterators, and Generators ES6 features?

Yes. These features were introduced with ECMAScript 2015, commonly called ES6. Modern JavaScript environments support them.

Conclusion

ES6 introduced several features that make JavaScript collections, object properties, and iteration more powerful.

  • Set stores unique values.
  • Map stores key-value pairs.
  • WeakSet tracks eligible references without strongly retaining them.
  • WeakMap associates data with eligible object or symbol keys.
  • Symbols create unique primitive values, often used as property keys.
  • Iterators provide a standard way to retrieve values one at a time.
  • Generators make it easier to create iterators using function* and yield.

Understanding these features helps you choose the right data structure and process values efficiently in modern JavaScript applications.