You see a problem that asks you to "count occurrences" or "find the most frequent element." You know you need to count things... but then what?
Do you use a Counter? A dict? How do you find the top-k most frequent elements efficiently? What if you need to count and then do something with those counts?
This is the gap between knowing how to count and mastering the frequency counter pattern.
The frequency counter is one of the most versatile hash map patterns. It appears in 50+ LeetCode problems, from easy "Valid Anagram" to hard "Top K Frequent Elements." But the pattern isn't just Counter(arr)—it's a systematic approach to counting, querying, and optimizing based on frequencies.
This guide will teach you:
- When to use frequency counters vs. other patterns
- The complete template (Python Counter, Java HashMap, JS Map)
- All major variations (most frequent, k frequent, frequency-based conditions)
- How to combine frequency counters with other patterns
- Common mistakes and how to avoid them
Master this, and frequency problems become pattern recognition, not guesswork.
What Is the Frequency Counter Pattern?
The core concept
The frequency counter pattern uses a hash map to count how many times each element appears, then uses those counts to make decisions or find answers.
Structure:
- Key: The element you're counting
- Value: How many times it appears (count/frequency)
Example:
arr = [1, 2, 2, 3, 3, 3]
freq = {1: 1, 2: 2, 3: 3}Why it works
Counting with a hash map is O(n) time, O(k) space where k = number of distinct elements.
Without hash map:
# Count how many times 3 appears - O(n) per query
count = sum(1 for x in arr if x == 3)With hash map:
# Build once - O(n)
freq = Counter(arr)
# Query - O(1)
count = freq[3]Real-world analogy
Imagine counting votes in an election:
- Without frequency map: Count ballots for each candidate separately (slow)
- With frequency map: Tally as you go, look up any candidate's count instantly (fast)
When to Use Frequency Counters
Quick checklist
Use frequency counter when:
- ✅ You need to count occurrences of elements
- ✅ You need to find most/least frequent elements
- ✅ You need to compare frequencies (anagrams, permutations)
- ✅ You need to check if frequency meets a condition
- ✅ You need to group by frequency
Phrases in problem statements
Strong frequency counter signals:
- "count the number of..."
- "most frequent / least frequent"
- "top k frequent"
- "first unique / non-repeating"
- "valid anagram" (same frequencies)
- "permutation" (same frequencies)
- "majority element" (frequency > n/2)
Frequency counter vs. other patterns
vs. Hash Set:
- Set: Only need presence (exists or not)
- Frequency counter: Need count (how many times)
vs. Sorting:
- Sorting: O(n log n), can find frequencies but slower
- Frequency counter: O(n), direct access to counts
vs. Array counting:
- Array: Only works for small, known range (e.g., 0-9, a-z)
- Frequency counter: Works for any hashable elements
The Universal Template
Python: Three Approaches
Approach 1: Counter (Recommended)
from collections import Counter
# Build frequency counter
freq = Counter(arr)
# Access count
count = freq[element] # Returns 0 if not found
# Most common
most_common = freq.most_common(k) # Returns [(elem, count), ...]
# Example
arr = ['a', 'b', 'a', 'c', 'a', 'b']
freq = Counter(arr)
print(freq) # Counter({'a': 3, 'b': 2, 'c': 1})
print(freq.most_common(2)) # [('a', 3), ('b', 2)]Pros: Clean, built-in, has useful methods
Cons: Slightly more memory overhead
Approach 2: Manual dict
# Build frequency counter
freq = {}
for elem in arr:
freq[elem] = freq.get(elem, 0) + 1
# Or with setdefault
freq = {}
for elem in arr:
freq.setdefault(elem, 0)
freq[elem] += 1Pros: More control, slightly faster
Cons: More verbose
Approach 3: defaultdict
from collections import defaultdict
freq = defaultdict(int)
for elem in arr:
freq[elem] += 1Pros: Clean, no need for .get()
Cons: Returns 0 for missing keys (can be surprising)
Recommendation: Use Counter for interviews (clearest intent).
Java Template
import java.util.*;
// Build frequency counter
Map<Character, Integer> freq = new HashMap<>();
for (char c : s.toCharArray()) {
freq.put(c, freq.getOrDefault(c, 0) + 1);
}
// Access count
int count = freq.getOrDefault(c, 0);
// Find most frequent (manual)
int maxFreq = 0;
char mostFrequent = ' ';
for (Map.Entry<Character, Integer> entry : freq.entrySet()) {
if (entry.getValue() > maxFreq) {
maxFreq = entry.getValue();
mostFrequent = entry.getKey();
}
}JavaScript Template
// Using Map
const freq = new Map();
for (const elem of arr) {
freq.set(elem, (freq.get(elem) || 0) + 1);
}
// Access count
const count = freq.get(elem) || 0;
// Find most frequent
let maxFreq = 0;
let mostFrequent = null;
for (const [elem, count] of freq.entries()) {
if (count > maxFreq) {
maxFreq = count;
mostFrequent = elem;
}
}
// Using object (for simple keys)
const freq = {};
for (const elem of arr) {
freq[elem] = (freq[elem] || 0) + 1;
}Pattern 1: Count and Compare
Use case: Compare if two collections have the same frequencies.
Problem: Valid Anagram (LeetCode 242)
Problem: Given two strings s and t, return true if t is an anagram of s.
Insight: Anagrams have identical character frequencies.
Solution 1: Two Counters
from collections import Counter
def isAnagram(s: str, t: str) -> bool:
return Counter(s) == Counter(t)Complexity: O(n) time, O(k) space where k = alphabet size
Solution 2: Single Counter
def isAnagram(s: str, t: str) -> bool:
if len(s) != len(t):
return False
freq = Counter(s)
for char in t:
if char not in freq:
return False
freq[char] -= 1
if freq[char] < 0:
return False
return TrueOptimization: Slightly more memory efficient (one counter instead of two).
Solution 3: Array Counter (if alphabet is small)
def isAnagram(s: str, t: str) -> bool:
if len(s) != len(t):
return False
# Only for lowercase English letters
count = [0] * 26
for char in s:
count[ord(char) - ord('a')] += 1
for char in t:
count[ord(char) - ord('a')] -= 1
if count[ord(char) - ord('a')] < 0:
return False
return all(c == 0 for c in count)When to use: Only if alphabet is small and known (a-z, 0-9).
Problem: Find All Anagrams (LeetCode 438)
Problem: Find all start indices of anagrams of p in string s.
Pattern: Frequency counter + sliding window
from collections import Counter
def findAnagrams(s: str, p: str) -> List[int]:
if len(p) > len(s):
return []
result = []
p_count = Counter(p)
window_count = Counter(s[:len(p)])
if window_count == p_count:
result.append(0)
# Slide window
for i in range(len(p), len(s)):
# Add new character
window_count[s[i]] += 1
# Remove old character
left_char = s[i - len(p)]
window_count[left_char] -= 1
if window_count[left_char] == 0:
del window_count[left_char]
# Check if anagram
if window_count == p_count:
result.append(i - len(p) + 1)
return resultKey insight: Combine frequency counter with sliding window for efficiency.
Pattern 2: Find Most/Least Frequent
Use case: Find elements with highest/lowest frequency.
Problem: Top K Frequent Elements (LeetCode 347)
Problem: Given an integer array nums and an integer k, return the k most frequent elements.
Solution 1: Counter + Heap
from collections import Counter
import heapq
def topKFrequent(nums: List[int], k: int) -> List[int]:
# Count frequencies
freq = Counter(nums)
# Use heap to find top k
return heapq.nlargest(k, freq.keys(), key=freq.get)Complexity: O(n + k log n) time, O(n) space
Solution 2: Counter.most_common()
from collections import Counter
def topKFrequent(nums: List[int], k: int) -> List[int]:
freq = Counter(nums)
return [num for num, count in freq.most_common(k)]Complexity: O(n + k log n) time (uses heap internally)
Solution 3: Bucket Sort (Optimal)
from collections import Counter
def topKFrequent(nums: List[int], k: int) -> List[int]:
freq = Counter(nums)
# Bucket sort: bucket[i] = elements with frequency i
buckets = [[] for _ in range(len(nums) + 1)]
for num, count in freq.items():
buckets[count].append(num)
# Collect top k from highest frequency buckets
result = []
for i in range(len(buckets) - 1, 0, -1):
result.extend(buckets[i])
if len(result) >= k:
return result[:k]
return resultComplexity: O(n) time, O(n) space (optimal!)
When to use each:
- Heap: General case, works for any k
- Bucket sort: When you need O(n) time (optimal)
Problem: Sort Characters By Frequency (LeetCode 451)
from collections import Counter
def frequencySort(s: str) -> str:
freq = Counter(s)
# Sort by frequency (descending), then by character (for tie-breaking)
sorted_chars = sorted(freq.items(), key=lambda x: (-x[1], x[0]))
# Build result
result = []
for char, count in sorted_chars:
result.append(char * count)
return ''.join(result)Alternative (bucket sort):
def frequencySort(s: str) -> str:
freq = Counter(s)
buckets = [[] for _ in range(len(s) + 1)]
for char, count in freq.items():
buckets[count].append(char)
result = []
for i in range(len(buckets) - 1, 0, -1):
for char in buckets[i]:
result.append(char * i)
return ''.join(result)Pattern 3: Frequency-Based Conditions
Use case: Check if frequencies meet specific conditions.
Problem: First Unique Character (LeetCode 387)
Problem: Find the first non-repeating character in a string.
from collections import Counter
def firstUniqChar(s: str) -> int:
freq = Counter(s)
# Find first character with frequency 1
for i, char in enumerate(s):
if freq[char] == 1:
return i
return -1Key insight: Build frequency map first, then iterate to find first unique.
Problem: Majority Element (LeetCode 169)
Problem: Find the element that appears more than ⌊n/2⌋ times.
Solution 1: Frequency Counter
from collections import Counter
def majorityElement(nums: List[int]) -> int:
freq = Counter(nums)
n = len(nums)
for num, count in freq.items():
if count > n // 2:
return numComplexity: O(n) time, O(n) space
Solution 2: Boyer-Moore Voting (Optimal)
def majorityElement(nums: List[int]) -> int:
candidate = None
count = 0
for num in nums:
if count == 0:
candidate = num
count += 1 if num == candidate else -1
return candidateComplexity: O(n) time, O(1) space (optimal!)
When to use:
- Frequency counter: General case, easy to understand
- Boyer-Moore: When space is critical
Problem: Check If All Characters Have Equal Frequency (LeetCode 1941)
from collections import Counter
def areOccurrencesEqual(s: str) -> bool:
freq = Counter(s)
counts = list(freq.values())
return len(set(counts)) == 1Insight: All frequencies should be the same → set of frequencies has size 1.
Advanced: Frequency Counter + Sliding Window
Combining frequency counter with sliding window is powerful for substring problems.
Problem: Permutation in String (LeetCode 567)
Problem: Check if s2 contains a permutation of s1.
from collections import Counter
def checkInclusion(s1: str, s2: str) -> bool:
if len(s1) > len(s2):
return False
s1_count = Counter(s1)
window_count = Counter(s2[:len(s1)])
if window_count == s1_count:
return True
# Slide window
for i in range(len(s1), len(s2)):
# Add new character
window_count[s2[i]] += 1
# Remove old character
left_char = s2[i - len(s1)]
window_count[left_char] -= 1
if window_count[left_char] == 0:
del window_count[left_char]
if window_count == s1_count:
return True
return FalsePattern: Fixed-size sliding window + frequency comparison.
For more on this combination, see Hash Map in Sliding Window.
Common Mistakes
Mistake 1: Not Handling Missing Keys
❌ Wrong:
freq = {}
for char in s:
freq[char] += 1 # KeyError if char not in freq!✅ Correct:
# Option 1: Use .get()
freq = {}
for char in s:
freq[char] = freq.get(char, 0) + 1
# Option 2: Use Counter
from collections import Counter
freq = Counter(s)
# Option 3: Use defaultdict
from collections import defaultdict
freq = defaultdict(int)
for char in s:
freq[char] += 1Mistake 2: Comparing Counters Incorrectly
❌ Wrong:
# Comparing lists of values (order matters!)
freq1 = Counter(s1)
freq2 = Counter(s2)
return list(freq1.values()) == list(freq2.values()) # Wrong!✅ Correct:
# Compare Counter objects directly
return Counter(s1) == Counter(s2)Mistake 3: Not Removing Zero Counts
❌ Wrong:
# In sliding window, leaving zero counts
window_count[left_char] -= 1
# Now window_count might have {char: 0}, which != Counter without that char✅ Correct:
window_count[left_char] -= 1
if window_count[left_char] == 0:
del window_count[left_char]Mistake 4: Using Array When Hash Map Is Needed
❌ Wrong:
# Assuming only lowercase letters, but input has uppercase too
count = [0] * 26 # Only handles a-z!✅ Correct:
# Use Counter for general case
freq = Counter(s)Rule: Only use array counting when alphabet is small and known.
Complexity Analysis
Time Complexity
Building frequency counter: O(n)
- Iterate through all n elements once
- Each insertion/update is O(1) average
Querying frequency: O(1)
- Hash map lookup
Finding top-k:
- With heap: O(n + k log n)
- With bucket sort: O(n)
Space Complexity
Frequency counter: O(k) where k = number of distinct elements
- Best case: O(1) (all elements same)
- Worst case: O(n) (all elements distinct)
Practical consideration: For strings with limited alphabet (a-z), space is O(26) = O(1).
Practice Problems
Beginner
- Valid Anagram (#242) - Basic frequency comparison
- Majority Element (#169) - Frequency condition
- First Unique Character (#387) - Find by frequency
- Intersection of Two Arrays II (#350) - Frequency matching
Intermediate
- Top K Frequent Elements (#347) - Heap or bucket sort
- Sort Characters By Frequency (#451) - Sort by frequency
- Find All Anagrams (#438) - Frequency + sliding window
- Permutation in String (#567) - Frequency + sliding window
- Group Anagrams (#49) - Frequency as grouping key
Advanced
- Minimum Window Substring (#76) - Frequency + sliding window
- Longest Substring with At Most K Distinct (#340) - Frequency + sliding window
- Task Scheduler (#621) - Frequency-based greedy
Summary
The frequency counter pattern is a fundamental hash map technique for counting and querying element occurrences.
Key principles:
- Count first, query later: Build frequency map in O(n), query in O(1)
- Choose the right tool: Counter for clarity, dict for control, array for small alphabets
- Combine with other patterns: Sliding window, heap, bucket sort
The template:
from collections import Counter
# Build
freq = Counter(arr)
# Query
count = freq[element]
# Top k
top_k = freq.most_common(k)When to use:
- ✅ Count occurrences
- ✅ Find most/least frequent
- ✅ Compare frequencies
- ✅ Frequency-based conditions
Common mistakes:
- ❌ Not handling missing keys
- ❌ Comparing incorrectly
- ❌ Not removing zero counts
- ❌ Using array when hash map is needed
Next steps:
- Master the complete hash map guide
- Learn Two Sum pattern for complement search
- Study grouping pattern for categorization
- Practice the beginner problem set
Master frequency counters, and counting problems become pattern recognition.
Want to Practice LeetCode Smarter?
LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.
Also compatible with Edge, Brave, and Opera
