A hash map trades memory for speed: spend O(n) extra space remembering what you've already seen, and turn an O(n) or O(n²) lookup into an O(1) average one. It's arguably the single highest-leverage pattern in this whole list, because so much of what follows — a sliding window's seen set above, a graph traversal's "visited" tracking, memoization in dynamic programming — is really hashing wearing a different name.
function twoSum(nums, target) {
const indexOf = new Map(); // value -> index seen so far
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (indexOf.has(complement)) {
return [indexOf.get(complement), i];
}
indexOf.set(nums[i], i); // record AFTER checking — see below
}
return null;
}
The brute-force version of this problem checks every pair — O(n²). Hashing turns it into a single pass: for each element, ask "have I already seen the value that would complete a pair with this one?" — an O(1) average lookup — instead of scanning the rest of the array to find out. Recording nums[i] after checking for its complement, rather than before, matters for a subtle reason: it stops an element from being paired with itself unless that same value genuinely appears twice in the array.
function isAnagram(a, b) {
if (a.length !== b.length) return false;
const counts = new Map();
for (const ch of a) counts.set(ch, (counts.get(ch) || 0) + 1);
for (const ch of b) {
if (!counts.has(ch)) return false;
counts.set(ch, counts.get(ch) - 1);
if (counts.get(ch) === 0) counts.delete(ch);
}
return counts.size === 0; // every count balanced back to zero
}
The same idea — a map from value to count — generalizes to grouping: bucket strings by a computed key (their sorted letters, say) and every anagram group falls into the same bucket automatically, without ever comparing strings pairwise.
One JavaScript-specific gotcha worth knowing: a plain object silently coerces every key to a string (obj[1] and obj["1"] are the same property), which breaks quietly the moment you try to use non-string keys like numbers, objects, or array references. Map doesn't have that problem — it accepts any value as a key, preserves insertion order, and exposes an actual .size instead of requiring Object.keys(obj).length. Default to Map for anything beyond the simplest string-keyed counting.
It's also worth remembering that "O(1) average" is doing real work in that phrase — a hash table's worst case is O(n) if enough keys collide into the same bucket, though for the well-distributed inputs interview problems use, the average case is the one that matters in practice.
Practice this on Code Lab: Hash Table