Implement a Set Data Structure with a Resizable Array, Then Improve It with Hashing
DisneySoftware Engineer · Technical ScreenLast updated
Tests low-level data structure design by implementing a set from scratch using an array-backed hash table, focusing on collision resolution, dynamic resizing, load factor management, and amortized complexity.
Problem Statement
Implement an integer set data structure, CustomHashSet, from scratch without using any built-in hash-based containers (such as Python's set or dict, Java's HashSet or HashMap, or C++'s std::unordered_set).
Your implementation should back its storage with an array and handle collisions using either separate chaining (e.g., linked list buckets) or open addressing (e.g., linear probing). Additionally, to maintain O(1) average time complexity, your set must dynamically resize (rehash) when the load factor exceeds a defined threshold.
Implement the following methods:
add(key: int) -> bool: Insertskeyinto the set. ReturnsTrueifkeywas not already present, orFalseif it was already in the set.remove(key: int) -> bool: Removeskeyfrom the set. ReturnsTrueifkeywas present and removed, orFalseotherwise.contains(key: int) -> bool: ReturnsTrueifkeyis present in the set, andFalseotherwise.size() -> int: Returns the total number of unique elements currently in the set.
Rehashing Requirements
- Initialize with an internal capacity of at least 8 or 16 buckets.
- Maintain a maximum load factor of
0.75(load_factor = size / capacity). When inserting an element causessize / capacity > 0.75, double the internal array capacity and rehash all existing elements.
Class Interface
class CustomHashSet:
def __init__(self):
pass
def add(self, key: int) -> bool:
pass
def remove(self, key: int) -> bool:
pass
def contains(self, key: int) -> bool:
pass
def size(self) -> int:
pass
Examples
Example 1
my_set = CustomHashSet()
my_set.add(1) # returns True
my_set.add(2) # returns True
my_set.add(1) # returns False (already present)
my_set.contains(1) # returns True
my_set.contains(3) # returns False
my_set.size() # returns 2
my_set.remove(2) # returns True
my_set.contains(2) # returns False
my_set.size() # returns 1
Constraints
-10^9 <= key <= 10^9- At most
10^5calls will be made in total across all operations. - You may not use built-in set, map, or hash table data structures.
- Average time complexity per operation should be
O(1). Worst-case time complexity without rehashing can beO(n)on hash collisions. - Space complexity should be
O(n + capacity).
Edge Cases to Consider
- Handling negative integer keys in hash index computations (e.g.,
hash(key) % capacitymust map to a non-negative index). - Deletions when using open addressing: avoid breaking probe chains by using tombstone markers (or use separate chaining).
- Large volumes of duplicate insertions should not trigger unnecessary resizing.
Hints
Hint 1
Choose between separate chaining (each bucket holds a linked list or dynamic list) and open addressing (linear probing); separate chaining is often simpler to implement correctly with deletions.
Hint 2
Remember to compute array indices safely for negative numbers using an expression such as `(hash(key) % capacity + capacity) % capacity`.
Hint 3
When rehashing into a doubled array, you cannot simply copy buckets over; each element's target bucket index must be recomputed using the new capacity modulus.
Follow-up questions
- How would you implement downward resizing (shrinking) when elements are deleted to reclaim memory, and what threshold would prevent thrashing?
- If you used open addressing with linear probing, how do tombstone markers affect lookup time over long sequences of additions and deletions?
- How would you make this data structure thread-safe for concurrent reads and writes without locking the entire hash table?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.