TypeScript Set
Introduced in ECMAScript 2015 (ES6), the Set object is a built-in collection designed to store unique values of any type. In the context of TypeScript, Set becomes even more powerful because it leverages generics to provide robust static type checking, ensuring that the elements you add to the collection conform to your defined type.
Unlike an Array, which allows duplicate entries and provides access via numerical indices, a Set is optimized for membership testing and ensuring data integrity through uniqueness. While arrays are better for ordered sequences where duplicates are allowed, Set is the superior choice when you need to perform frequent lookups, deletions, or simply want to maintain a collection of distinct items.
Working with Generics and Initialization
One of the primary advantages of using Set in TypeScript is the ability to define the type of elements it will contain using the Set<T> syntax.
Explicit Type Annotation
When you want to be explicit about the type of data the set will hold, you can declare it during instantiation:
const numbers = new Set<number>();
numbers.add(1);
numbers.add(2);
numbers.add(2); // This duplicate will be ignored
console.log(numbers.size); // Output: 2
You can also handle complex types by using union types:
const mixedCollection = new Set<string | number>();
mixedCollection.add("user_123");
mixedCollection.add(404);
Type Inference
TypeScript is often smart enough to infer the type of a Set if you initialize it with an array or another iterable:
const names = new Set(["Alice", "Bob", "Charlie"]);
// TypeScript automatically infers this as Set<string>
Creating Sets from Iterables
You can initialize a Set using various iterable objects, such as arrays or even strings:
const fromArray = new Set<number>([1, 2, 3, 4]);
const fromString = new Set<string>("hello");
// Result: Set { 'h', 'e', 'l', 'o' }
Core API: Properties and Methods
The Set API is concise and focused on managing unique membership. Here are the most commonly used members:
add(value: T): this: Adds a new element to the set. Because it returns the set instance itself, you can use method chaining.has(value: T): boolean: Returnstrueif the value exists in the set, andfalseotherwise. This is highly efficient.delete(value: T): boolean: Removes the specified element. It returnstrueif the element was successfully found and removed, andfalseif the element did not exist.clear(): void: Removes all elements from the set, resetting its size to zero.size: number: A property that returns the total number of unique elements currently in the set.
const tags = new Set<string>();
// Using method chaining
tags.add("typescript").add("javascript").add("react");
console.log(tags.has("typescript")); // true
console.log(tags.size); // 3
tags.delete("javascript");
console.log(tags.size); // 2
tags.clear();
console.log(tags.size); // 0
Iterating Over a Set
Since Set maintains the insertion order, iterating through its elements is predictable. You cannot access elements by index (e.g., set[0] will not work), so you must use iteration protocols.
Using for...of
The most idiomatic way to loop through a set is the for...of loop:
const colors = new Set(["red", "green", "blue"]);
for (const color of colors) {
console.log(color);
}
Using forEach
Similar to arrays, Set provides a forEach method. Note a unique quirk: the callback function receives the value as both the first and second arguments (since Sets do not have keys).
colors.forEach((value, _unusedValue, set) => {
console.log(value, `Total size: ${set.size}`);
});
Additionally, you can use values(), keys(), and entries(). In a Set, keys() and values() behave identically.
Practical Patterns: Conversion and Deduplication
Converting Between Set and Array
In many real-world scenarios, you may need to switch between a Set (for uniqueness) and an Array (for indexing or sorting).
const mySet = new Set([1, 2, 3]);
// Method 1: Using the spread operator (most common)
const arr1 = [...mySet];
// Method 2: Using Array.from()
const arr2 = Array.from(mySet);
The "De-duplication" Trick
The most popular use case for Set is removing duplicates from an existing array. This can be achieved in a single, elegant line:
const rawData = [1, 2, 2, 3, 3, 3, 4, 4, 5];
const uniqueData = [...new Set(rawData)];
console.log(uniqueData); // [1, 2, 3, 4, 5]
Note: If your target environment is older than ES2015, ensure your tsconfig.json is configured with "target": "ES2015" or higher, or enable "downlevelIteration".
Implementing Set Mathematics
While JavaScript/TypeScript provides the foundation, it does not natively include methods for mathematical set operations like union, intersection, or difference. However, we can easily implement these using generics to maintain type safety.
/** Returns a new set containing all elements from both sets */
function union<T>(a: Set<T>, b: Set<T>): Set<T> {
return new Set([...a, ...b]);
}
/** Returns a new set containing only elements present in both sets */
function intersection<T>(a: Set<T>, b: Set<T>): Set<T> {
const result = new Set<T>();
for (const item of a) {
if (b.has(item)) {
result.add(item);
}
}
return result;
}
/** Returns a new set containing elements in 'a' that are NOT in 'b' */
function difference<T>(a: Set<T>, b: Set<T>): Set<T> {
const result = new Set<T>();
for (const item of a) {
if (!b.has(item)) {
result.add(item);
}
}
return result;
}
// Usage Example
const setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);
console.log([...union(setA, setB)]); // [1, 2, 3, 4, 5]
console.log([...intersection(setA, setB)]); // [3]
console.log([...difference(setA, setB)]); // [1, 2]
Advanced Considerations and Best Practices
Equality and Reference Types
Set uses the SameValueZero algorithm to determine equality. This has two important implications:
NaNhandling: Unlike some other comparison methods,NaNis considered equal toNaNin aSet, so you won't get duplicateNaNentries.- Object References: For objects, arrays, or functions,
Setcompares references, not content. Two different object literals with the same properties are considered unique.
const set = new Set();
set.add({ id: 1 });
set.add({ id: 1 });
console.log(set.size); // 2 (because they are different object references)
Defensive Programming with ReadonlySet<T>
If you are passing a Set into a function that should only read the data without modifying it, use the ReadonlySet<T> type. This prevents accidental calls to .add() or .delete().
function processData(data: ReadonlySet<string>) {
data.forEach(item => console.log(item));
// data.add("new"); // Error: Property 'add' does not exist on type 'ReadonlySet<string>'
}
Memory Management: WeakSet
If you need to store objects as keys but want to avoid memory leaks, consider WeakSet. A WeakSet holds "weak" references to its elements, meaning if there are no other references to an object, it can be garbage collected even if it is still inside the WeakSet. Note that WeakSet is not iterable and can only store objects.
Configuration Tip
To ensure TypeScript recognizes the Set type, verify that your tsconfig.json includes ES2015 (or a later version) in the lib array:
{
"compilerOptions": {
"lib": ["ES2015", "DOM"]
}
}