CodeOath
← All posts
Data Structures & Algorithms43 min total · 16 parts

DSA Patterns for Coding Interviews: The Techniques Behind Almost Every Problem

Part 4 of 16 · ~2 min

Hashing Patterns

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.

"Have I seen this before?"

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.

Frequency counting

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