awesome-everything RU
↑ Back to the climb

Algorithms from zero

Hashing: code reading

Crux Read real hash-table and hash-map snippets, predict their behaviour, and pick the highest-leverage fix: chaining lookup, doubling resize, complement and frequency bugs.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 14 min

Hashing bugs hide in code that looks correct. Read each snippet, predict what it actually does under load or on edge cases, then pick the fix a careful engineer would make first.

Goal

Practise the loop you run on every hash-map task: read the code, trace it on the awkward input, and reach for the precise fix — not a vague ‘use a hash map’ wave of the hand.

Snippet 1 — chained insert and lookup

class HashTable {
  constructor(size = 8) {
    this.buckets = Array.from({ length: size }, () => []);
  }
  hash(key) {
    let h = 0;
    for (const ch of key) h = (h * 31 + ch.charCodeAt(0)) % this.buckets.length;
    return h;
  }
  set(key, value) {
    const b = this.buckets[this.hash(key)];
    b.push([key, value]);          // always push
  }
  get(key) {
    const b = this.buckets[this.hash(key)];
    for (const [k, v] of b) if (k === key) return v;
    return undefined;
  }
}
Quiz

You call set('a', 1) then set('a', 2), then get('a'). What is returned, and what is the bug?

Snippet 2 — resize on load factor

set(key, value) {
  if ((this.count + 1) / this.buckets.length > 0.75) this.resize();
  const b = this.buckets[this.hash(key)];
  b.push([key, value]);
  this.count++;
}
resize() {
  const old = this.buckets;
  this.buckets = Array.from({ length: old.length * 2 }, () => []);
  for (const bucket of old)
    for (const [k, v] of bucket) {
      const i = this.hash(k);       // re-hash with the NEW length
      this.buckets[i].push([k, v]);
    }
}
Quiz

Why must resize re-hash every key with the new array length instead of copying buckets straight across?

Snippet 3 — two-sum with the complement pattern

function twoSum(nums, target) {
  const seen = new Map();           // value -> index
  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);           // record AFTER the check
  }
  return null;
}
Quiz

Why does the check for need happen BEFORE seen.set(nums[i], i), and what breaks if you swap the two lines?

Snippet 4 — anagram grouping by frequency key

function groupAnagrams(words) {
  const groups = new Map();
  for (const w of words) {
    const key = w.split('').sort().join('');   // canonical key
    if (!groups.has(key)) groups.set(key, []);
    groups.get(key).push(w);
  }
  return [...groups.values()];
}
Quiz

This groups anagrams correctly. For long strings, what is the cost of the key, and what is the cheaper canonical key with the same grouping result?

Recap

Every hash-map task is read in code: a chained set must update an existing key in place, not blindly push; resize must re-hash with the new modulus or lookups go to the wrong bucket; the complement pattern records after it checks so an element never pairs with itself; and grouping needs a canonical key that is invariant across the group — a character count beats a sort on the hot path. Read the awkward input first, then pick the precise fix.

Continue the climb ↑Hashing: build a resizing hash map
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.