Start learning free

LESSON 240 OF 348 · HASH TABLE

Hash Sets in TypeScript: Using Set<T>

Lesson Read the full explanation

Hash Sets are collections of unique items i.e no element can be repeated.

Hash Sets were designed to give us a way of adding and looking for unique values in a collection in a quick manner.

The values in a Hash Set can be either simple primitives like strings or integers as well as more complex object types like object literals or arrays.

In JavaScript, the Hash Set is implemented as the Set object.


Creation:

To create a new empty Set, you use the following syntax:

let mySet = new Set();

Creating an empty Set takes O(1) time.


Constructing from iterable

The Set constructor also accepts an optional iterable object. If you pass an iterable object to the Set constructor, all the unique elements of the object will be added to the new set:

let elements = [1, '2', 'apple', 1];
let mySet = new Set(elements);

console.log(mySet); // Set {1, '2', 'apple'}

This takes O(n) time, where n is the number of elements in the iterable object.


Adding elements:

To add an element to the set, you use the add() method:

let mySet = new Set(['a', 'b', 'c']);

// Adding new elements:
mySet.add('d');

// Adding an existing element:
mySet.add('a'); // nothing happens

console.log(mySet); // Set {'a', 'b', 'c', 'd'}

The Set first checks if that element already exists and if so, it does nothing and also doesn't raise an error.

Adding an element to a Set takes O(1) time.


Checking if a value exists:

To check if a Set has a specific element, you use the has() method. The has() method returns true if the set contains the element, otherwise, it returns false.

let mySet = new Set(['a', 'b', 'c']);

let exist = mySet.has('a');
console.log(exist); // true

exist = chars.has('z');
console.log(exist); // false

Checking if a value exists in a Set takes O(1) time.


Removing elements:

To delete a specified element from a set, you use the delete() method:

let mySet = new Set(['a', 'b', 'c', 'd']);

// Deleting elements:
mySet.delete('a');

// Deleting a non-existent element:
mySet.delete('z'); // does nothing

console.log(mySet); // Set {'b', 'c', 'd'}

The Set first checks if that key exists and if not, it does nothing and also doesn't raise an error, so it's safe to delete non-existing elements.

Removing an element from a Set takes O(1) time.


Iterating over the Set values:

If we want to iterate over values of the Set, we can use the for loop along with the of operator:

let mySet = new Set(['b', 'c', 'c', 'd']);

for (let val of mySet) {
    console.log(val);
}

// This will print the following:
// b
// c
// d

Iterating over a Set takes O(n) time.


Space Complexity

A Set uses O(n) space, where n is the number of elements existing in the Set.


Assignment
Follow the Coding Tutorial and let's play with some Hash Sets.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 6
    Ctrl + Enter to check

    TYPESCRIPT · HASH TABLE

    Hash Set in TypeScript, explained

    What a hash set is

    A hash set is a collection of unique values. Adding a value that is already there changes nothing, and there is no way to store the same value twice. In return, the three operations you use most are fast: adding a value, checking whether a value is present, and removing a value each take O(1) time on average.

    In TypeScript the hash set is the built-in Set, the same object JavaScript has. Reach for it whenever the question is "have I seen this before?" or "which distinct values are in here?". Scanning an array to answer those takes O(n) per question. A set answers in constant time.

    Creating a set

    An empty set is created with new Set(). Because an empty set gives TypeScript nothing to infer from, say what it holds with a type argument:

    const seen = new Set<number>();
    seen.add(1);
    seen.add("a"); // compiler error: a string is not a number

    The type argument and the annotation are checked only by the compiler. When the code is compiled to JavaScript they disappear, so the running program has no idea that seen was meant to hold numbers.

    A set can also be built from any iterable. Passing an array keeps one copy of each value:

    const elements = [1, '2', 'apple', 1];
    const mySet = new Set(elements);
    console.log(mySet);
    Set(3) { 1, '2', 'apple' }

    TypeScript infers Set<string | number> here. Notice that 1 and '2' are different values: a set never converts between types. Building a set from an array of n elements takes O(n) time.

    The operations

    • add(value) inserts the value and returns the set itself, so calls can be chained. Adding an existing value does nothing and raises no error.
    • has(value) returns true or false.
    • delete(value) removes the value and returns true if it was there, false if not. Deleting a missing value is safe.
    • size is a property, not a method. Write mySet.size, with no parentheses.
    • for (const v of mySet) visits every value in the order it was first inserted.
    const nums = new Set<number>([3, 1, 2]);
    console.log(nums.add(2) === nums); // true: add returns the set
    console.log(nums.delete(2));       // true
    console.log(nums.delete(99));      // false
    console.log(nums.has(1), nums.size);
    true
    true
    false
    true 2
    

    Iterating a set takes O(n) time and the set itself uses O(n) space.

    Turning a set back into an array

    A set has no indexing and no map or filter. To get an array, spread it or use Array.from:

    const unique = [...new Set([3, 1, 3, 2, 1])];
    console.log(unique); // [ 3, 1, 2 ]

    What counts as "the same" value

    This is where sets surprise people:

    • Objects and arrays compare by reference. new Set([[1], [1]]).size is 2, because the two arrays are different objects even though their contents match. To store pairs, turn them into a string key such as "1,2" first.
    • Primitives compare by value. Two equal numbers or two equal strings are one entry.
    • NaN equals itself in a set. new Set([NaN, NaN]).size is 1, even though NaN === NaN is false.
    • The type does not matter at run time. A Set<number> built from data that really holds strings, for example parsed input cast with as, will happily hold both 1 and "1".

    The exercise, step by step

    1. Start with an array that contains a duplicate. fruitsArray is annotated as string[] and holds "apples", "bananas" and "apples".
    2. Build the set from it. fruits is a Set<string> created with new Set(fruitsArray). The second "apples" is dropped, so the set holds two values.
    3. Add a new value. fruits.add("oranges") grows the set to three values. Adding "apples" again would have changed nothing.
    4. Check for a value. fruits.has("apples") is the condition of an if. It is true here, so the program prints We have apples.
    5. Remove a value. fruits.delete("bananas") takes it out.
    6. Loop over what is left. A for...of loop with let fruit prints each remaining value.
    let fruitsArray: string[] = ["apples", "bananas", "apples"];
    
    let fruits: Set<string> = new Set(fruitsArray);
    
    fruits.add("oranges");
    
    if (fruits.has("apples")) {
        console.log("We have apples");
    }
    
    fruits.delete("bananas");
    
    for (let fruit of fruits) {
        console.log(fruit);
    }

    The output is:

    We have apples
    apples
    oranges

    The loop prints apples before oranges because a set remembers insertion order. The duplicate and the deleted "bananas" are both gone.

    Common mistakes

    • Writing mySet.size(). size is a number, so calling it is an error.
    • Indexing a set. mySet[0] is not valid. Spread the set into an array first if you need positions.
    • Expecting add to report whether the value was new. It returns the set. Check has first, or compare size before and after.
    • Using a set for objects you want compared by content. Two equal-looking objects are two entries.
    • Using Set for counts. A set remembers that a value exists, not how many times it appeared. For counts, use a Map or an object.

    Key takeaways

    • Set<T> stores each value once; add, has and delete take O(1) time on average.
    • Build one from an array to remove duplicates, and spread it to get an array back.
    • Primitives compare by value and objects by reference.
    • Iteration follows insertion order, and a set of n values uses O(n) space.
    • The type argument is a compile-time check only.
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free