Hash Sets
Hash sets and hash maps: O(1) average lookup, collision strategies, and the patterns that turn quadratic problems into linear ones.
DSA — hash sets + maps
EXAMPLE
// ===== The model =====
// HashSet: store unique items, fast 'contains'
// HashMap: key -> value, fast 'get / set'
// Both rely on a HASH function distributing keys evenly across buckets.
// ===== Average complexity =====
// Insert / Lookup / Delete: O(1) amortised
// Worst case: O(n) under collisions (rare with a good hash + low load factor)
// ===== JavaScript Sets and Maps =====
const seen = new Set();
seen.add(1); seen.add(2); seen.add(1);
seen.has(1); // true
seen.size; // 2
seen.delete(2);
const m = new Map();
m.set('alex', 30);
m.set('sam', 25);
m.get('alex'); // 30
m.has('alex'); // true
m.size; // 2
// ===== The most common interview pattern =====
// Two-sum: find indices i, j such that nums[i] + nums[j] == target
function twoSum(nums, target) {
const seen = new Map();
for (let i = 0; i < nums.length; i++) {
const need = target - nums[i];
if (seen.has(need)) return [seen.get(need), i];
seen.set(nums[i], i);
}
return [];
}
// O(n) time vs O(n^2) for nested loops.
// ===== Group anagrams =====
function groupAnagrams(words) {
const m = new Map();
for (const w of words) {
const key = [...w].sort().join('');
if (!m.has(key)) m.set(key, []);
m.get(key).push(w);
}
return [...m.values()];
}
// ===== First non-repeating character =====
function firstUnique(s) {
const counts = new Map();
for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
for (let i = 0; i < s.length; i++) {
if (counts.get(s[i]) === 1) return i;
}
return -1;
}
// ===== When NOT to use a hash set =====
// - You need ordered traversal (use TreeSet / SortedSet)
// - Hash function is expensive (BigInt keys, complex objects)
// - Adversarial inputs (hashes are pseudo-random per process; tests should not rely on order)
// ===== Implementing 'unique' efficiently =====
// Convert array to set; back to array:
const unique = [...new Set([1, 2, 2, 3, 3, 3])]; // [1, 2, 3]
// ===== Set algebra =====
const a = new Set([1, 2, 3]);
const b = new Set([3, 4]);
// Union:
const u = new Set([...a, ...b]);
// Intersection:
const i = new Set([...a].filter(x => b.has(x)));
// Difference:
const d = new Set([...a].filter(x => !b.has(x)));
// (Modern ES2025 adds Set.prototype.union / intersection / difference natively.)
// ===== Custom objects as keys (the gotcha) =====
const m2 = new Map();
m2.set({ id: 1 }, 'a');
m2.get({ id: 1 }); // undefined (different object reference)
// Use a primitive key (id, JSON.stringify) or a custom hash.
// ===== Patterns to internalise =====
// - Hash set / map for any 'have we seen this?' or 'lookup by key' problem
// - Replace nested loops with hash lookups when possible
// - Build counts maps for frequency problems
// - Sort + hash key for grouping by 'equivalence'
// ===== Pitfalls =====
// - Object keys compared by reference, not value (JS Map)
// - Iteration order: insertion order in JS Map/Set (helpful), undefined in Python set
// - Hash collisions on adversarial inputs (DoS risk with string keys)
// - Mutating the key after insertion -> lost entry
Why it matters
Hash sets and maps collapse most quadratic loops to linear. Counts, grouping, dedup, two-sum, first-unique — all built on the same trick: trade memory for lookup. Master Set + Map + a habit of asking "have I seen this before?" and most array problems get easier.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
function hasDup(nums) {
const seen = new Set();
for (const n of nums) {
if (seen.has(n)) return true;
seen.add(n);
}
return false;
}
Try it Yourself »
Discussion
Loading…