PYTHON • PROBLEM-SOLVING PATTERNS

Counting with dict/Counter — Use counting/frequency with dict/Counter

Master the art of tallying element frequencies to solve problems efficiently in Python.

Historical Context & Motivation

Counting things is one of the oldest operations in computing. Long before modern programming languages existed, mathematicians and early computer scientists needed ways to tally frequencies — how many times each item appears in a dataset. From counting words in a book to analyzing letter frequencies for codebreaking during World War II, frequency analysis has been a fundamental tool. In Python, this pattern evolved from manual loops into elegant, built-in solutions.

1940s
Frequency Analysis in Codebreaking
Alan Turing and colleagues used letter frequency counting to crack the Enigma cipher, demonstrating the power of tallying occurrences in large datasets.
1991
Python 1.0 — The dict Type
Guido van Rossum released Python with built-in dictionaries, giving programmers a hash-table-based structure perfect for counting items manually.
2003
Python 2.3 — defaultdict Appears
The collections module introduced defaultdict, which eliminated the need to check if a key exists before incrementing a count.
2011
Python 3.1 — Counter Arrives
The Counter class was added to the collections module, providing a purpose-built tool for counting hashable objects with handy methods like most_common().
Today
Universal Problem-Solving Pattern
Counting with dict and Counter is now one of the most common patterns in coding interviews, data analysis, and real-world Python applications.

The core question this lesson addresses is straightforward: given a collection of items, how do you efficiently determine how many times each unique item appears? While you could sort the data and scan through it, Python offers much faster and cleaner approaches using dictionaries and the Counter class.

Core Principles & Definitions

Before diving into code, let's understand the foundational ideas behind frequency counting. A frequency map (also called a frequency table or histogram) is a mapping from each unique element to the number of times it appears. In Python, we build frequency maps using either a plain dict or the specialized collections.Counter class. Both rely on the same underlying principle: keys represent unique elements, and values represent their counts.

1

Frequency Map

A data structure that pairs each unique element with its count. In Python, this is a dictionary where keys are elements and values are integers representing how often each element appears.
2

Hashable Keys

Dictionary keys must be hashable — meaning they have a fixed hash value. Strings, numbers, and tuples are hashable; lists and other dicts are not. This is what makes lookups run in O(1) average time.
3

The dict Approach

Using a plain dictionary, you iterate through a collection and manually increment counts. You use dict.get(key, 0) or check membership with 'in' to handle keys that don't exist yet.
4

The Counter Class

Counter is a dict subclass from the collections module. You pass it any iterable, and it automatically builds the frequency map. It also provides methods like most_common() and supports arithmetic between Counters.
5

O(n) Time Complexity

Both approaches scan the input once, making them O(n) in time where n is the number of elements. Each lookup and insertion in a dict averages O(1), so the total work scales linearly.
✦ KEY TAKEAWAY
Think of a frequency map like a teacher taking attendance. As each student walks in, the teacher puts a tally mark next to their name. At the end, you know exactly how many times each name was called. A Python dict is like doing it by hand; Counter is like having a smart clipboard that tallies automatically.

Visual Explanation — How Counting Works

The diagram below traces how a frequency map is built step by step from a list of fruits. Each time we encounter an element, we either create a new entry with a count of 1 or increment an existing entry. Watch how the dictionary grows as we move through the input.

Each row shows how the dictionary state changes as we process one element at a time. When an element already exists (highlighted rows), its count is incremented rather than a new key being created.

Notice how at steps 3, 5, and 6, the element already exists in the dictionary. Instead of adding a new key, we simply increment the existing value. This is the core logic behind both the manual dict approach and the Counter class — the difference is that Counter handles all of this automatically when you pass it an iterable.

How It Works — Code Patterns

Pattern 1: Manual Counting with dict

The most basic pattern uses a plain dictionary and the get method. For each element in your iterable, you call counts.get(item, 0) + 1. The second argument to get() is a default value that's returned when the key doesn't exist yet. This avoids a KeyError and makes the code clean.

DICT.GET PATTERN
counts = {} for item in data: counts[item] = counts.get(item, 0) + 1
counts — the frequency dictionary; data — the input iterable; item — current element; get(item, 0) returns the current count or 0 if the item hasn't been seen yet.

Pattern 2: Using if/else

An alternative manual approach checks membership with the in keyword. If the key already exists, increment it; otherwise, set it to 1. This pattern is more verbose but can be easier to read when you're first learning.

IF/ELSE PATTERN
counts = {} for item in data: if item in counts: counts[item] += 1 else: counts[item] = 1
item in counts checks whether the key already exists. This runs in O(1) average time thanks to hashing.

Pattern 3: collections.Counter

The Counter class from the collections module does all the counting in a single line. You just pass it any iterable — a list, string, tuple, or even a generator — and it returns a Counter object (which behaves like a dictionary). It also comes with useful methods like most_common(n) which returns the n most frequent elements as a list of tuples.

COUNTER PATTERN
from collections import Counter counts = Counter(data) # counts.most_common(2) → top 2 elements # counts[item] → count of item (0 if missing)
Counter automatically handles missing keys by returning 0 instead of raising a KeyError, making it safer and more convenient than a plain dict.
💡 Counter vs. dict — When Missing Keys Are Accessed
If you access a missing key on a plain dict, Python raises a KeyError. But a Counter returns 0 for any missing key. This means Counter('hello')['z'] gives 0 — no error, no crash. This behavior is extremely useful in many problem-solving scenarios.

Common Use Cases & Classification

Frequency counting shows up in a surprisingly wide range of problems. Once you learn to recognize when a problem is really asking 'how many of each?', you'll find that dict and Counter simplify your solutions dramatically. Below is a visual classification of the most common use cases you'll encounter in coding exercises and real applications.

Frequency counting problems fall into three main categories: string-based problems like anagram detection, array/list problems like finding duplicates, and data analysis tasks like tallying votes or tracking inventory.

The key insight is to look for trigger words in problem descriptions. Phrases like "how many times", "most frequent", "duplicate", and "appears more than" all signal that frequency counting is the right approach. When you spot these cues, your first instinct should be to build a frequency map.

Worked Example — Finding the Most Frequent Character

Let's walk through a complete problem: given a string, find the character that appears most often. If there's a tie, return the one that comes first alphabetically. We'll solve it two ways — first with a plain dict, then with Counter.

Most Frequent Character (dict approach)
1
Step 1 — Define the InputOur input string is s = "banana". We need to count each character's frequency and then find the maximum.
2
Step 2 — Build the Frequency MapWe create an empty dictionary and loop through each character. Using counts[char] = counts.get(char, 0) + 1, we build the map.
counts = {'b': 1, 'a': 3, 'n': 2}
3
Step 3 — Find the Maximum CountWe use max_count = max(counts.values()) to find the highest frequency.
max_count = 3
4
Step 4 — Handle Ties (Alphabetical Tiebreaker)We collect all characters with the maximum count: candidates = [ch for ch, cnt in counts.items() if cnt == max_count]. Then we return min(candidates) to pick the alphabetically first one.
candidates = ['a'], so the answer is 'a'
5
Step 5 — Counter ShortcutWith Counter, this simplifies to: from collections import Counter; counts = Counter(s); print(counts.most_common(1)). The most_common(1) method returns [('a', 3)], giving us both the character and its count in one call.
Most frequent character: 'a' with 3 occurrences.
📝 Full Code — Both Versions
Dict version: counts = {}; [counts.__setitem__(c, counts.get(c,0)+1) for c in s] — but use a regular for-loop for clarity. Counter version: Counter(s).most_common(1)[0][0] — a single expression that returns the answer directly.

dict vs. Counter — Strengths & Limitations

Both approaches accomplish the same goal, but they each have trade-offs. Understanding when to use a plain dict versus Counter will make you a more effective problem solver. The table below breaks down the key differences.

Comparison of dict and Counter for frequency counting
FeaturePlain dictCounter
Import needed?No — built-inYes — from collections
Lines of code to count3–5 lines (loop required)1 line (pass iterable)
Missing key behaviorRaises KeyErrorReturns 0
most_common() methodNot availableBuilt-in, returns sorted list
Arithmetic (+, −)Manual merge neededSupports +, −, &, | operators
FlexibilityFull control over logicLess customizable
PerformanceO(n) — sameO(n) — same (slight overhead)
🔑 WHEN TO USE WHICH
Use a plain dict when you need custom logic during counting (like filtering elements or doing extra processing in the loop). Use Counter when you want clean, readable code and need features like most_common() or Counter arithmetic. In coding interviews, Counter is usually preferred because it saves time and shows you know Python's standard library.

Connection to Advanced Techniques

Frequency counting is a foundational skill that connects to many more advanced problem-solving techniques. Once you're comfortable with dict and Counter, you'll find them appearing inside larger algorithms. Here's how the basic pattern scales up.

How basic counting connects to advanced patterns
Basic ConceptAdvanced Extension
Count characters in a stringSliding window frequency maps — track counts in a moving window to solve substring problems
Find the most common elementTop-K problems using Counter.most_common(k) combined with heaps for large datasets
Check if two strings are anagramsGroup anagrams — use sorted-string or Counter as dictionary keys to cluster words
Count duplicates in a listBucket sort — use frequency as an index to sort in O(n) time without comparisons
Simple frequency mapdefaultdict(list) for grouping — values become lists of items instead of counts

The sliding window technique is particularly important. It combines frequency counting with two pointers to solve problems like "find the longest substring with at most K distinct characters." You maintain a Counter as your window expands and contracts, adding and removing elements. This runs in O(n) time and is a staple of coding interviews at every level.

🚀 Looking Ahead
As you advance, you'll also encounter defaultdict(int) from the collections module. It works like a dict but automatically initializes missing keys to 0 (or any default you specify). It's the middle ground between a plain dict and Counter — more flexible than Counter but less boilerplate than a plain dict.

Practice Problems

PROBLEM 1 — CONCEPTUAL
Explain in your own words why a Python dictionary is a good data structure for counting frequencies. What property of dictionaries makes lookups fast?
PROBLEM 2 — BASIC CALCULATION
Given the list nums = [4, 1, 2, 1, 4, 4, 3, 2, 1], write code using a plain dict (with the .get() method) to build a frequency map. What is the resulting dictionary?
PROBLEM 3 — INTERMEDIATE
Write a function are_anagrams(s1, s2) that returns True if two strings are anagrams of each other (contain exactly the same characters with the same frequencies). Use Counter.
PROBLEM 4 — APPLIED
A teacher has a list of test scores: scores = [85, 92, 78, 92, 85, 100, 78, 85, 92, 78, 100, 85]. Write code using Counter to: (a) find the most common score, (b) determine how many unique scores there are, and (c) list all scores that appear more than 2 times.
PROBLEM 5 — CRITICAL THINKING
The "majority element" problem asks you to find an element that appears more than n/2 times in a list of length n (it's guaranteed to exist). You could solve this with Counter, but can you think of why Counter might not be the most memory-efficient approach? Describe both the Counter solution and suggest what additional optimization might be possible.

Lesson Summary

Frequency counting is one of the most versatile patterns in Python programming. You can build a frequency map using a plain dict with the .get() method or an if/else check, or you can use collections.Counter for a one-line solution that also provides powerful methods like most_common() and arithmetic operators. Both approaches run in O(n) time thanks to hash-table-based O(1) lookups.

Look for trigger words in problems — "how many times," "most frequent," "duplicates," and "appears more than" — to know when to apply this pattern. Common applications include anagram detection, finding duplicates, top-K problems, and data analysis tasks. As you advance, this pattern becomes the foundation for techniques like the sliding window and bucket sort.

Varsity Tutors • Python • Counting with dict/Counter