Welcome to the second module of our course, where we'll focus on patterns involving hashing, arrays, and strings. In our last lesson, we concluded with a powerful example, "Subarray Sum Equals K," where we used a hash map in a clever way to count the frequencies of prefix sums. This allowed us to find a solution in linear time, a significant improvement over brute-force methods.
Today, we will formalize and expand on that idea. The technique of tracking counts in a hash map is not just a one-off trick; it's a fundamental problem-solving pattern known as frequency counting. This lesson is dedicated to mastering this pattern. We will explore how to use hash maps to efficiently count item occurrences and generalize this to solve a broader class of aggregation problems, which are extremely common in coding interviews.
What is a Hash Map? A Quick Refresher
As a developer, you use hash maps regularly—in Python, they are your everyday dict. Their power lies in a simple promise: on average, you can add, remove, and look up a key-value pair in constant time, or . This property is the key to unlocking massive performance gains, often turning a slow quadratic nested-loop solution into a fast linear single-pass algorithm.
The core idea is to trade space for time. We use extra memory (the hash map) to store information that allows us to avoid re-computing or re-searching parts of our data.
Under the Hood: How Do They Work?
Given your computer engineering background, it's worth taking a moment to appreciate the mechanism that provides this magic. A hash map is essentially a clever wrapper around a standard array.
- Hash Function: When you insert a key-value pair, a hash function converts your key into an integer, which is then mapped to an index in the underlying array. For example, the string "name" might be hashed to the integer 231, which corresponds to index
231 % array_size. - Storage: The value is then stored at that array index.
- Collisions: What happens if two different keys hash to the same index? This is called a collision. A common way to resolve this is separate chaining, where each array slot, or "bucket," holds a small linked list of all the key-value pairs that hashed to that index.

This diagram illustrates the core mechanism of a hash map. Keys ("Jess", "Jake", "May", "Amy") are converted into hash values (309, 283, 231). These hashes map to storage locations. Notice the collision at index 231, where both "May" and "Amy" are stored, demonstrating a collision resolution strategy like separate chaining.
When you look up a key, the hash map re-computes its hash, jumps to the correct array index in , and if there's a linked list (due to collisions), it does a quick linear scan of that very short list to find your key. As long as the hash function distributes keys evenly and the map isn't too full, these lists remain tiny, and the average time for all operations stays close to .
To dive a little deeper into these mechanics, the following video provides a great overview.
Hash Tables: Hash Functions, Sets, & Maps - DSA Course in Python Lecture 4
This lecture from Greg Hogg's DSA course explains the core theory of hash tables, including hash functions, collision handling, and the crucial concept of what makes a key "hashable."
Watch the introduction to see how a hash function maps a key to an array index. Next, understand the most common collision strategy, separate chaining. Finally, pay close attention to the discussion on hashable vs. mutable types. In Python, this is why you can use a tuple as a dictionary key, but not a list.
The Frequency Counting Pattern
The most direct application of a hash map is to count the frequency of elements in a collection. This pattern is simple, powerful, and forms the basis for solving many interview problems.
Imagine you're given an array ["apple", "orange", "apple", "pear", "orange", "apple"] and asked to count each fruit. The hash map is the perfect tool for this.

This image shows the essence of frequency counting. A hash map stores key-value pairs (A:1, B:2, etc.), which directly correspond to the frequencies of items shown in the bar chart.
The algorithm is as follows:
- Initialize an empty hash map (e.g.,
counts = {}). - Iterate through each element in the input collection.
- For each element, check if it's already a key in your hash map.
- If it is, increment the corresponding value:
counts[element] += 1. - If it isn't, add the element as a new key with an initial value of 1:
counts[element] = 1.
- If it is, increment the corresponding value:
This simple process sounds easy, but it contains a classic pitfall. What happens when you try to increment the count for a key that doesn't exist yet?
counts = {}
item = "apple"
# This will raise a KeyError because the key "apple" is not yet in the dictionary.
counts[item] += 1
The video below expertly walks through this exact problem and presents the standard, robust way to handle it.
This video by David Blaikie focuses on aggregation and provides a crystal-clear explanation of the fundamental frequency counting logic, including how to handle new keys.
First, watch the walkthrough of how a dictionary is built up step-by-step to count items. Next, see the demonstration of the KeyError that occurs when you try to access a key that hasn't been seen before. Finally, understand the standard solution using an if key in dict check to initialize new keys correctly before incrementing.
The manual implementation using an if/else check is fundamental, and you should be prepared to write it in an interview. However, as a Python developer, you have more specialized tools at your disposal that make this pattern even more concise.
Hash Tables Interview Questions & Tips for Senior Engineers
This article from interviewing.io discusses common use cases for hash tables in interviews and highlights some Python-specific utilities.
Read the section on "Frequency Counts," which reinforces this pattern's importance. Then, study the "Pro Tip" section that introduces Python's Counter and defaultdict. Pay attention to the code examples showing how defaultdict(int) automatically handles the KeyError by providing a default value of 0 for new keys, and how Counter can perform the entire frequency count in a single line.
Here's a summary of your options in Python for frequency counting:
data = ["apple", "orange", "apple", "pear", "orange", "apple"]
# 1. Manual check (most fundamental)
counts_manual = {}
for item in data:
if item in counts_manual:
counts_manual[item] += 1
else:
counts_manual[item] = 1
# 2. Using .get() with a default value (more concise)
counts_get = {}
for item in data:
counts_get[item] = counts_get.get(item, 0) + 1
# 3. Using defaultdict (Pythonic and efficient)
from collections import defaultdict
counts_defaultdict = defaultdict(int)
for item in data:
counts_defaultdict[item] += 1
# 4. Using Counter (the ultimate shortcut)
from collections import Counter
counts_counter = Counter(data)
# All methods produce the same result:
# {'apple': 3, 'orange': 2, 'pear': 1}
While Counter is powerful, an interviewer might ask you to implement the logic manually to test your core understanding. The defaultdict approach is often a happy medium, demonstrating both understanding and knowledge of Python's standard library.
Generalizing to Aggregation
The frequency counting pattern is a specific type of a more general pattern: aggregation. Instead of just incrementing a count by 1, you can aggregate any value, such as summing up revenues, concatenating strings, or appending to a list.
Let's return to the "Subarray Sum Equals K" problem from our last lesson. We used a hash map prefix_counts to store {prefix_sum: count}. At each step, we calculated the current_sum and looked for current_sum - k. This was an aggregation problem where we were counting the occurrences of each prefix sum.
Hash Map Patterns for Coding Interviews: The Essential Guide ...
The AlgoArk blog provides an excellent walkthrough of this pattern.
Please review the section "Example 3 — Subarray Sum Equals K". Notice how the code prefix_counts[prefix] = prefix_counts.get(prefix, 0) + 1 is exactly the frequency counting pattern we just discussed. Also, re-read the crucial point under "Common Mistakes to Avoid" about forgetting the base case prefix_counts = {0: 1}.
This combination of a prefix sum and a hash map for aggregation is a powerful pattern that solves many problems that seem difficult at first glance.
Conclusion
You've now formally learned one of the most indispensable hash map patterns for coding interviews. By using a hash map to count frequencies or aggregate data, you can often develop an elegant and efficient linear-time solution.
Key Takeaways:
- Hash Maps for Optimization: Their average time for lookups, insertions, and deletions allows you to eliminate nested loops and turn algorithms into ones.
- Frequency Counting Pattern: Iterate through a collection, using a hash map to store each unique item as a key and its count as the value.
- Handling New Keys: The most common implementation challenge is handling the first appearance of a key. You can solve this with an
if/elsecheck, the.get()method, ordefaultdict. - Aggregation: Frequency counting is a specific form of aggregation. The same pattern can be used to sum values or collect items associated with a key.
- Pythonic Tools: For a Python developer,
defaultdictandCounterfrom thecollectionsmodule are powerful, concise tools for implementing this pattern.
In our next lesson, we will explore another essential hash map pattern: using the hash map to find a "complement" value. This is the core idea behind solving the classic "Two Sum" problem and its many variations.