ES6 introduced Map and Set for more powerful data structures. This article covers how to use these collection types.

Set Collection

Basic Operations

// Create Set
const set = new Set();
const setFromArray = new Set([1, 2, 3, 3, 4]);  // Auto deduplication

// Add elements
set.add(1);
set.add(2);
set.add(2);  // Duplicate ignored
set.add('hello');
set.add({ name: 'Alice' });

// Chaining
set.add(3).add(4).add(5);

// Check existence
set.has(1);   // true
set.has(10);  // false

// Delete element
set.delete(1);  // Returns true/false

// Clear all
set.clear();

// Size
set.size;  // 0

Iteration Methods

const set = new Set(['a', 'b', 'c']);

// forEach
set.forEach((value, valueAgain, set) => {
  console.log(value);  // 'a', 'b', 'c'
});

// for...of
for (const value of set) {
  console.log(value);
}

// Iterator methods
set.keys();    // SetIterator {'a', 'b', 'c'}
set.values();  // SetIterator {'a', 'b', 'c'}
set.entries(); // SetIterator {'a' => 'a', 'b' => 'b', 'c' => 'c'}

// Convert to array
const arr = [...set];
const arr2 = Array.from(set);

Practical Use Cases

// Array deduplication
const arr = [1, 2, 2, 3, 3, 3, 4];
const unique = [...new Set(arr)];  // [1, 2, 3, 4]

// Set operations
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);

// Union
const union = new Set([...setA, ...setB]);
// Set {1, 2, 3, 4, 5, 6}

// Intersection
const intersection = new Set(
  [...setA].filter(x => setB.has(x))
);
// Set {3, 4}

// Difference
const difference = new Set(
  [...setA].filter(x => !setB.has(x))
);
// Set {1, 2}

// Symmetric difference
const symmetricDiff = new Set(
  [...setA].filter(x => !setB.has(x)).concat(
    [...setB].filter(x => !setA.has(x))
  )
);
// Set {1, 2, 5, 6}

Map Collection

Basic Operations

// Create Map
const map = new Map();
const mapFromEntries = new Map([
  ['key1', 'value1'],
  ['key2', 'value2']
]);

// Set key-value pairs
map.set('name', 'Alice');
map.set(123, 'number key');
map.set({ id: 1 }, 'object key');

// Chaining
map.set('a', 1).set('b', 2).set('c', 3);

// Get value
map.get('name');  // 'Alice'
map.get('xxx');   // undefined

// Check existence
map.has('name');  // true

// Delete
map.delete('name');  // Returns true/false

// Clear all
map.clear();

// Size
map.size;

Iteration Methods

const map = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3]
]);

// forEach
map.forEach((value, key, map) => {
  console.log(`${key}: ${value}`);
});

// for...of
for (const [key, value] of map) {
  console.log(`${key}: ${value}`);
}

// Iterator methods
map.keys();    // MapIterator {'a', 'b', 'c'}
map.values();  // MapIterator {1, 2, 3}
map.entries(); // MapIterator {'a' => 1, 'b' => 2, 'c' => 3}

// Conversions
const arr = [...map];              // [['a', 1], ['b', 2], ['c', 3]]
const obj = Object.fromEntries(map);  // { a: 1, b: 2, c: 3 }

Map vs Object

Map vs Object Comparison:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Key Types                                         │
│   ├── Map: Any type (objects, functions, etc.)     │
│   └── Object: Strings or Symbols only              │
│                                                     │
│   Order                                             │
│   ├── Map: Maintains insertion order               │
│   └── Object: No order guarantee (except integers) │
│                                                     │
│   Size                                              │
│   ├── Map: size property                           │
│   └── Object: Need Object.keys().length            │
│                                                     │
│   Performance                                       │
│   ├── Map: Better for frequent add/delete          │
│   └── Object: Faster for static lookups            │
│                                                     │
│   Serialization                                     │
│   ├── Map: No direct JSON support                  │
│   └── Object: Native JSON support                  │
│                                                     │
└─────────────────────────────────────────────────────┘
// Objects as keys
const userRoles = new Map();
const user1 = { id: 1, name: 'Alice' };
const user2 = { id: 2, name: 'Bob' };

userRoles.set(user1, 'admin');
userRoles.set(user2, 'user');

userRoles.get(user1);  // 'admin'

// Cannot do this with regular objects
const objRoles = {};
objRoles[user1] = 'admin';  // Key becomes '[object Object]'
objRoles[user2] = 'user';   // Overwrites the previous one

WeakSet

Features and Usage

// WeakSet can only store object references
const weakSet = new WeakSet();

const obj1 = { a: 1 };
const obj2 = { b: 2 };

weakSet.add(obj1);
weakSet.add(obj2);

weakSet.has(obj1);  // true
weakSet.delete(obj1);

// Cannot add primitives
// weakSet.add(1);  // TypeError

// Weak reference: objects are garbage collected when no other references
let temp = { c: 3 };
weakSet.add(temp);
temp = null;  // Object may be garbage collected

// Not iterable
// weakSet.forEach(...)  // Does not exist
// [...weakSet]          // Not supported

Practical Use Cases

// Mark objects (e.g., processed DOM nodes)
const processedNodes = new WeakSet();

function processNode(node) {
  if (processedNodes.has(node)) {
    return;  // Already processed
  }

  // Process node
  doSomething(node);
  processedNodes.add(node);
}

// Private data
const privateData = new WeakSet();

class MyClass {
  constructor() {
    privateData.add(this);
  }

  isValid() {
    return privateData.has(this);
  }
}

WeakMap

Features and Usage

// WeakMap keys must be objects
const weakMap = new WeakMap();

const key1 = { id: 1 };
const key2 = { id: 2 };

weakMap.set(key1, 'value1');
weakMap.set(key2, 'value2');

weakMap.get(key1);   // 'value1'
weakMap.has(key2);   // true
weakMap.delete(key1);

// Cannot use primitives as keys
// weakMap.set('key', 'value');  // TypeError

// Weak reference keys
let tempKey = { id: 3 };
weakMap.set(tempKey, 'value3');
tempKey = null;  // Key-value pair may be garbage collected

Practical Use Cases

// Store private data
const privateProps = new WeakMap();

class Person {
  constructor(name, age) {
    privateProps.set(this, { name, age });
  }

  getName() {
    return privateProps.get(this).name;
  }

  getAge() {
    return privateProps.get(this).age;
  }
}

const person = new Person('Alice', 25);
person.getName();  // 'Alice'
// Cannot access private data externally

// Cache computation results
const cache = new WeakMap();

function expensiveComputation(obj) {
  if (cache.has(obj)) {
    return cache.get(obj);
  }

  const result = /* complex calculation */ obj.value * 2;
  cache.set(obj, result);
  return result;
}

// Associate data with DOM elements
const elementData = new WeakMap();

function setElementData(element, data) {
  elementData.set(element, data);
}

function getElementData(element) {
  return elementData.get(element);
}

// When element is removed, associated data is automatically released

Performance Comparison

// Large data operation performance test
const size = 100000;

// Array lookup O(n)
const arr = Array.from({ length: size }, (_, i) => i);
console.time('Array includes');
arr.includes(size - 1);
console.timeEnd('Array includes');

// Set lookup O(1)
const set = new Set(arr);
console.time('Set has');
set.has(size - 1);
console.timeEnd('Set has');

// Object lookup O(1)
const obj = Object.fromEntries(arr.map(i => [i, true]));
console.time('Object lookup');
obj[size - 1];
console.timeEnd('Object lookup');

// Map lookup O(1)
const map = new Map(arr.map(i => [i, true]));
console.time('Map get');
map.get(size - 1);
console.timeEnd('Map get');

Best Practices Summary

Collection Type Selection Guide:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Use Set                                           │
│   ├── Need to store unique values                  │
│   ├── Fast existence checking                      │
│   └── Set operations (intersection, union, etc.)   │
│                                                     │
│   Use Map                                           │
│   ├── Keys are not strings                         │
│   ├── Need to maintain insertion order             │
│   └── Frequent key-value add/delete                │
│                                                     │
│   Use WeakSet/WeakMap                               │
│   ├── Prevent memory leaks                         │
│   ├── Store associated data for objects            │
│   └── Implement private properties                 │
│                                                     │
└─────────────────────────────────────────────────────┘
TypeKey TypeIterableWeak Reference
Set-YesNo
MapAnyYesNo
WeakSetObjectNoYes
WeakMapObjectNoYes

Use collection types wisely for more efficient data management.