Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Design a HashSet
DSA

Design a HashSet

Understand how to build a hash set and support efficient insertion, deletion, and membership operations.

A hashset is a hashmap that stores keys only — no values.

Same skeleton as the hashmap lesson:

index = hash(key) % bucketCount, collisions chain inside the bucket.

One shortcut worth knowing:

If keys are small non-negative ints (say < 10^6), skip hashing entirely — a plain boolean array is O(1) everything.


Core Template

Buckets + chaining with a tiny hash you can verify by eye — collisions, probes and removal all in one run:

HashSet with Chaining

Insert/lookup/delete over fixed buckets using key % SIZE.

hash = key % SIZE picks a bucket; collisions are stored as a chain (linked list) in that bucket. add prepends if absent, get walks the chain comparing keys, remove unlinks. Average O(1) if the load factor stays low.

HASH BUCKETS VISUALIZER
Steps
bucket 0
bucket 1
bucket 2
bucket 3
bucket 4
Press ▶ to animate, or step through manually.
Variables
keys: ← → space F
Pseudocode

                        1
                        bucket = key % SIZE
                      
                        2
                        add(key): if not in chain: prepend
                      
                        3
                        contains(key): walk the chain
                      
                        4
                        remove(key): unlink the node
                      
class MyHashSet {
    private static final int SIZE = 1009;
    private List<Integer>[] buckets;

    public MyHashSet() {
        buckets = new List[SIZE];
    }

    public void add(int key) {
        int h = key % SIZE;
        if (buckets[h] == null) buckets[h] = new ArrayList<>();
        if (!buckets[h].contains(key)) buckets[h].add(key);
    }

    public void remove(int key) {
        List<Integer> chain = buckets[key % SIZE];
        if (chain != null) chain.remove(Integer.valueOf(key));
    }

    public boolean contains(int key) {
        List<Integer> chain = buckets[key % SIZE];
        return chain != null && chain.contains(key);
    }
}
class MyHashSet:
    def __init__(self):
        self.size = 1009
        self.buckets = [[] for _ in range(self.size)]

    def add(self, key: int) -> None:
        chain = self.buckets[key % self.size]
        if key not in chain:
            chain.append(key)

    def remove(self, key: int) -> None:
        chain = self.buckets[key % self.size]
        if key in chain:
            chain.remove(key)

    def contains(self, key: int) -> bool:
        return key in self.buckets[key % self.size]
class MyHashSet {
    static constexpr int SIZE = 1009;
    vector<unordered_set<int>> buckets{SIZE};

public:
    MyHashSet() {}

    void add(int key)      { buckets[key % SIZE].insert(key); }
    void remove(int key)   { buckets[key % SIZE].erase(key); }
    bool contains(int key) { return buckets[key % SIZE].count(key) > 0; }
};
class MyHashSet {
  #size = 1009;
  #buckets = Array.from({ length: this.#size }, () => new Set());

  add(key) {
    this.#buckets[key % this.#size].add(key);
  }

  remove(key) {
    this.#buckets[key % this.#size].delete(key);
  }

  contains(key) {
    return this.#buckets[key % this.#size].has(key);
  }
}

Pattern: The Boolean-Array Shortcut

When constraints bound the keyspace (0 ≤ key ≤ 10^6), the “hash” is the identity function:

class MyHashSet {
    private final boolean[] seen = new boolean[1_000_001];

    public void add(int key)    { seen[key] = true; }
    public void remove(int key) { seen[key] = false; }
    public boolean contains(int key) { return seen[key]; }
}
class MyHashSet:
    def __init__(self):
        self.seen = [False] * 1_000_001

    def add(self, key):       self.seen[key] = True
    def remove(self, key):    self.seen[key] = False
    def contains(self, key):  return self.seen[key]
class MyHashSet {
    vector<bool> seen{vector<bool>(1'000'001, false)};

public:
    void add(int key)      { seen[key] = true; }
    void remove(int key)   { seen[key] = false; }
    bool contains(int key) { return seen[key]; }
};
class MyHashSet {
  #seen = Array(1_000_001).fill(false);

  add(key)         { this.#seen[key] = true; }
  remove(key)      { this.#seen[key] = false; }
  contains(key)    { return this.#seen[key]; }
}

Bounded int keys → direct-address array. Anything else → buckets + chaining.


Common Mistakes

Duplicate adds.

add must check membership first (or use a set structure) — duplicates silently corrupt size-based logic.


Java autobox trap.

chain.remove(key) removes by INDEX for List<Integer> when key is an int. Use remove(Integer.valueOf(key)).


Using the shortcut with unbounded/negative keys.

The boolean array needs a known, modest range. Negative or huge keys need real hashing.


Complexity

OperationAverageWorst
addO(1)O(n)
containsO(1)O(n)
removeO(1)O(n)
Boolean-array variantO(1) guaranteed

My Private Notes

Notes are auto-saved locally to this device.