How to Recognise When a Hash Map Can Improve Your LeetCode Solution

When solving algorithm problems, one of the most useful skills is recognising patterns.

A common example is identifying when a hash map or hash set can replace repeated searches and reduce the time complexity of a solution.

In many cases, a problem that initially appears to require an O(n²) solution can be reduced to O(n) by using a hash-based data structure.

But how can you recognise these situations quickly?

What is a hash map?

A hash map stores data as key-value pairs.

const user = new Map();
user.set("name", "Mhayk");
user.set("age", 39);
console.log(user.get("name")); // Mhayk

Internally, the key is processed by a hash function, which determines where the value should be stored.

Hash maps usually provide the following average time complexities:

OperationAverage complexity
InsertO(1)
SearchO(1)
DeleteO(1)

The worst case can be O(n), particularly when many collisions occur, but modern implementations are designed to minimise this.

The main question to ask

When reading a problem, ask yourself:

Do I need to find out quickly whether I have already seen something?

If the answer is yes, a hash map or hash set may be useful.

This is especially true when your first solution contains nested loops.

for (let i = 0; i < numbers.length; i++) {  
  for (let j = i + 1; j < numbers.length; j++) {    
      // Compare every pair  
   }
}

This approach is usually O(n²) because each element is compared with many other elements.

A hash map can often replace the inner loop with a constant-time lookup.

Example: Two Sum

Consider the classic Two Sum problem:

Given an array of integers and a target, return the indices of two numbers whose sum is equal to the target.

A brute-force approach compares every possible pair.

function twoSum(numbers, target) {
  for (let i = 0; i < numbers.length; i++) {
    for (let j = i + 1; j < numbers.length; j++) {
      if (numbers[i] + numbers[j] === target) {
        return [i, j];
      }
    }
  }
  return [];
}

The time complexity is:

O(n²)

For each number, however, we already know which other value we need.

required value = target - current value

We can store previously seen values in a hash map.

function twoSum(numbers, target) {
  const seen = new Map();
  for (let i = 0; i < numbers.length; i++) {
    const requiredValue = target - numbers[i];
    if (seen.has(requiredValue)) {
      return [seen.get(requiredValue), i];
    }
    seen.set(numbers[i], i);
  }
  return [];
}

Now the array is traversed only once.

The average time complexity becomes:

O(n)

The space complexity is:

O(n)

This is a classic example of exchanging additional memory for better execution time.

When should you use a Set?

Use a Set when you only need to know whether a value exists.

For example:

Does the array contain any duplicate values?

function containsDuplicate(numbers) {
  const seen = new Set();
  for (const number of numbers) {
    if (seen.has(number)) {
      return true;
    }
    seen.add(number);
  }
  return false;
}

The set does not need to associate the value with any additional information.

It only answers:

Have I seen this value before?

When should you use a Map?

Use a Map when you need to associate a key with extra information.

Common examples include:

value -> index
character -> frequency
prefix sum -> number of occurrences
word signature -> group of words

For example, counting character frequencies:

function countCharacters(text) {
  const frequencies = new Map();
  for (const character of text) {
    const currentCount = frequencies.get(character) ?? 0;
    frequencies.set(character, currentCount + 1);
  }
  return frequencies;
}

Common LeetCode patterns involving hash maps

There are several recurring patterns where hash maps are particularly useful.

1. Detecting duplicates

Typical questions include:

  • Does the array contain duplicates?
  • Has this element appeared before?
  • Is there a repeated character?

A Set is usually the first structure to consider.

2. Counting frequencies

Typical questions include:

  • How many times does each value appear?
  • Which value appears most frequently?
  • Are two strings anagrams?

A hash map can store the frequency of each element.

frequency.set(value, (frequency.get(value) ?? 0) + 1);

3. Finding a complement

This pattern appears when one value must be combined with another to satisfy a condition.

Examples include:

  • two values whose sum equals a target;
  • two values whose difference equals a target;
  • checking whether a required counterpart exists.

For Two Sum, the complement is:

const complement = target - currentValue;

Instead of searching the entire array, you check the hash map.

4. Grouping elements

Hash maps are also useful when elements can be represented by a shared key.

For example, in Group Anagrams:

"eat"
"tea"
"ate"

All three words can be transformed into the same sorted key:

"aet"

The hash map can then associate that key with a list of words.

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

5. Prefix sums

Hash maps are frequently combined with prefix sums.

This happens in problems involving:

  • subarray sums;
  • counting subarrays;
  • finding previous cumulative values;
  • matching a current sum with an earlier sum.

Suppose the current prefix sum is:

currentSum

To find a subarray whose sum is k, we look for:

currentSum - k

If that value has appeared before, then a valid subarray exists.

This is one of the most important intermediate LeetCode patterns.

6. Sliding window problems

Hash maps and sets are also common in sliding window problems.

A typical example is:

Find the length of the longest substring without repeating characters.

The window moves through the string while a set tracks the characters currently inside it.

function lengthOfLongestSubstring(text) {
  const characters = new Set();
  let left = 0;
  let longest = 0;
  for (let right = 0; right < text.length; right++) {
    while (characters.has(text[right])) {
      characters.delete(text[left]);
      left++;
    }
    characters.add(text[right]);
    longest = Math.max(longest, right - left + 1);
  }
  return longest;
}

In this case, the set provides fast membership checks while the window expands and contracts.

A quick checklist

When reading a problem, ask these questions:

  1. Do I need to check whether something has appeared before?
  2. Do I need to count occurrences?
  3. Do I need fast membership checks?
  4. Am I repeatedly searching through an array?
  5. Do I have nested loops because I am comparing every pair?
  6. Can I transform each element into a useful key?
  7. Do I need to associate an element with an index, count or group?

If the answer to any of these is yes, consider using a Map or Set.

Hash map or sorting?

A hash map is not always the best option.

Sorting may be preferable when:

  • the order of the elements matters;
  • you need to process values from smallest to largest;
  • the problem can be solved with two pointers;
  • you want to reduce additional memory usage;
  • you need range-based comparisons.

For example, Two Sum can also be solved by sorting the values and using two pointers.

That solution usually takes:

O(n log n)

It may use less additional memory, but preserving the original indices can make the implementation more complicated.

Time versus space

Hash maps often improve execution time by using more memory.

A common transformation is:

Before:
Time: O(n²)
Space: O(1)

After:
Time: O(n)
Space: O(n)

This is known as a time-space trade-off.

You are using additional memory to avoid repeated work.

A useful mental shortcut

A helpful rule is:

If your solution repeatedly searches for something that has already been processed, store it in a hash map.

Another useful rule is:

If an inner loop exists only to check whether a value is present, try replacing it with Map.has() or Set.has().

These two questions alone can help identify many common LeetCode solutions.

Final thoughts

Hash maps are not simply data structures to memorise.

They are tools for avoiding repeated work.

The key skill is recognising when information from previous iterations can be stored and reused.

Whenever you see duplicates, frequencies, complements, grouping, previous values, prefix sums or fast existence checks, a hash map should be one of the first options you consider.

With enough practice, recognising these patterns becomes almost automatic.