Skip to main content

Hash Maps and Sets

When a Hash Map Is the Wrong Choice

Written by Updated

Reaching for a Map by reflex is the second most common mistake after nested loops. It is fast at one thing - exact lookup - and unremarkable at everything else.

It has no useful order

A Map remembers insertion order, and nothing else. It cannot tell you the smallest key, the next key after this one, or everything between two values. Any of those means you want a sorted array or a tree.

The question a Map cannot answer

javascript

const ages = new Map([["ada", 36], ["grace", 45], ["alan", 41]])

// A Map cannot do this without walking everything:
//   who is the youngest?
//   who is between 35 and 42?

// Sorted, both are easy.
const sorted = [...ages.entries()].sort((a, b) => a[1] - b[1])
console.log(sorted[0])   // ["ada", 36] — youngest

That sort is O(n log n). If you only need the answer once, it beats maintaining a second structure.

It costs real memory

A Set of ten million numbers is not free, and on a constrained runtime it may not fit. When the problem says constant space, a hash map is being explicitly ruled out - that is the hint to use two pointers instead.

Same job, no allocation

javascript

// With a Set — O(n) time, O(n) space.
function hasPairWithSum(nums, target) {
  const seen = new Set()
  for (const n of nums) {
    if (seen.has(target - n)) return true
    seen.add(n)
  }
  return false
}

// Sorted input, two pointers — O(n) time, O(1) space.
function hasPairSorted(sorted, target) {
  let left = 0
  let right = sorted.length - 1

  while (left < right) {
    const sum = sorted[left] + sorted[right]
    if (sum === target) return true
    if (sum < target) left++
    else right--
  }

  return false
}

console.log(hasPairWithSum([2, 4, 5], 9))  // true
console.log(hasPairSorted([2, 4, 5], 9))   // true

On small inputs it loses

Building a Map has overhead. Over ten items a nested loop is usually faster in wall-clock terms, whatever Big O says. Big O describes growth, not small cases - and most real arrays are small.

Keys are compared by identity

This is the one that produces silent bugs. Two objects with identical contents are different keys:

The identity trap

javascript

const map = new Map()

map.set({ id: 1 }, "first")
console.log(map.get({ id: 1 }))   // undefined — a different object

// Use a primitive key instead.
const byId = new Map()
byId.set(1, "first")
console.log(byId.get(1))          // "first"

Choosing

  • Exact lookup, membership, counting - Map or Set.
  • Smallest, largest, ranges, sorted output - sorted array.
  • Constant space required, input sorted - two pointers.
  • Fewer than a few dozen items - whatever is clearest.