Python • Collections and Strings

Frequency Counting with Dicts — Count frequencies with dictionaries (collections.Counter intro)

Learn how to tally occurrences in data using Python dictionaries and the powerful collections.Counter class.

Code braces with terminal and markup symbols representing Python programming.
Inside this lesson 10 sections 5 questions 8 figures

SECTION 1

Historical Context & Motivation

Counting how often something appears is one of the oldest tasks in computing. Whether you are analyzing survey results, counting words in a document, or tallying votes in an election, the core operation is the same: walk through a collection of items and keep a running total for each unique value. Before high-level languages existed, programmers had to write detailed loops in assembly or C, manually managing arrays of counters. When Python arrived in the early 1990s, its built-in dictionary type made frequency counting far simpler—just map each item to its count. Then, in Python 2.7 and 3.1, the standard library introduced collections.Counter, a purpose-built class that automates the counting pattern entirely.

1991 • Python 1.0 Released

Guido van Rossum releases Python with built-in dictionaries, giving programmers a natural key-value data structure for counting and mapping.

2001 • Python 2.2 — Iterators Arrive

Python introduces iterators and generators, making it easy to loop through any sequence—a critical building block for frequency counting patterns.

2009 • collections.Counter Introduced

Python 2.7 and 3.1 add the Counter class to the collections module, formalizing the frequency-counting pattern into a reusable, well-tested tool.

2020s • Data Science & NLP Adoption

Frequency counting becomes foundational in natural language processing, data analytics, and competitive programming—Counter is used daily by millions of developers.

The fundamental question is straightforward: given a list of items, how do you determine the count of each unique item quickly and cleanly? Dictionaries give you the building blocks, and collections.Counter wraps those building blocks into an elegant, ready-made solution. Understanding both approaches will make you a more versatile programmer.

SECTION 2

Core Principles & Definitions

Before diving into code, let's pin down the key ideas. A frequency count is simply a mapping from each unique item to the number of times it appears in some collection. In Python, a dictionary (or dict) stores key-value pairs, making it the natural structure for this task—each key is an item and each value is its count.

1

Dictionary (dict)

A mutable, unordered collection of key-value pairs. Keys must be unique and hashable (strings, numbers, tuples). Values can be anything.
2

Frequency / Tally

The number of times a specific element appears in a sequence. For example, the letter 'a' might appear 5 times in a sentence.
3

The Counting Pattern

Loop through items; for each item, check if it is already a key in the dict. If yes, increment its value by 1. If no, set its value to 1.
4

collections.Counter

A dictionary subclass from Python's standard library designed specifically for counting hashable objects. It automates the counting loop in a single call.
5

most_common(n)

A Counter method that returns the n most frequently occurring elements as a list of (item, count) tuples, sorted from highest to lowest count.
✦

KEY TAKEAWAY

Think of a dictionary-based frequency count like a tally sheet at a voting booth. Each candidate's name is a key, and every time you see their name on a ballot, you add a tick mark (increment the value). collections.Counter is like handing the entire stack of ballots to a machine that produces the finished tally sheet instantly.

SECTION 3

Visual Explanation — How the Counting Loop Works

The diagram below traces the manual counting process step by step. We start with a list of fruit names and an empty dictionary. As the loop visits each item, the dictionary grows—new keys are created with a count of 1, and existing keys have their counts incremented.

Manual Frequency Counting — Step by StepInput list:'apple''banana''apple''cherry''banana''apple'StepCurrent ItemActionDict State1'apple'Key not found → create with 1{'apple': 1}2'banana'Key not found → create with 1{'apple': 1, 'banana': 1}3'apple'Key found → increment to 2{'apple': 2, 'banana': 1}4'cherry'Key not found → create with 1{'apple': 2, 'banana': 1, 'cherry': 1}5'banana'Key found → increment to 2{'apple': 2, 'banana': 2, 'cherry': 1}6'apple'Key found → increment to 3{'apple': 3, 'banana': 2, 'cherry': 1}Final Result'apple': 3'banana': 2'cherry': 1Each unique fruit becomes a key; its count becomes the value.
The diagram traces six iterations of the counting loop. Notice how 'apple' is encountered three times, so its count grows from 1 to 2 to 3 across steps 1, 3, and 6.

As you can see, the logic is repetitive: check if the key exists, then either create it or update it. This is exactly the tedium that dict.get() and collections.Counter are designed to eliminate. In the next sections, we will explore these cleaner approaches.

SECTION 4

How It Works — Three Approaches to Counting

There are three common ways to count frequencies in Python, each one more concise than the last. Understanding all three helps you read other people's code and choose the right tool for a given situation.

Approach 1 — Manual if/else Check

The most explicit way is to use an if statement inside a for loop. You test whether the item is already a key in the dictionary using the in operator. If it is, you add 1 to its value. If not, you set the value to 1.

MANUAL IF/ELSE PATTERN

counts=foritemindata:ifitemincounts:counts[item]+=1else:counts[item]=1counts = {} for item in data: if item in counts: counts[item] += 1 else: counts[item] = 1

counts — the dictionary accumulating frequencies; data — any iterable (list, string, tuple); item — each element visited by the loop.

Approach 2 — Using dict.get()

The dict.get(key, default) method returns the current value for key if it exists, or default if it doesn't. By setting the default to 0, you can collapse the if/else into a single line. This is more Pythonic and reduces the loop body to just two lines.

DICT.GET() PATTERN

counts=foritemindata:counts[item]=counts.get(item,0)+1counts = {} for item in data: counts[item] = counts.get(item, 0) + 1

counts.get(item, 0) returns the current count if the key exists, or 0 if it doesn't. Adding 1 then handles both cases.

Approach 3 — collections.Counter

The Counter class from the collections module does all the work for you. Pass any iterable to it, and it returns a dictionary-like object with items as keys and counts as values. Because Counter is a subclass of dict, you can use all normal dictionary operations on the result.

COUNTER PATTERN

fromcollectionsimportCountercounts=Counter(data)from collections import Counter counts = Counter(data)

That's it—one import and one line of code. counts now behaves like a dictionary but also offers methods like most_common(n) and arithmetic operations.

💡

Don't Forget the Import!

Counter lives in the collections module, which is part of Python's standard library (you don't need to install anything). Just add from collections import Counter at the top of your file.

SECTION 5

Counter Methods & Features

While a plain dictionary gives you the basics, Counter offers several extra features that make data analysis tasks much easier. Let's explore the most useful ones.

collections.Counter — Key Methods Overviewfrom collections import Countermost_common(n)c = Counter('aabbbcccc')c.most_common(2)[('c', 4), ('b', 3)]elements()c = Counter(a=2, b=3)list(c.elements())['a', 'a', 'b', 'b', 'b']subtract()c = Counter(a=4, b=2)c.subtract({'a': 1, 'b': 1})Counter({'a': 3, 'b': 1})Arithmetic (+, −, &, |)a = Counter(x=3, y=1)b = Counter(x=1, y=4)a + b → Counter({'y':5,'x':4})Missing keys return 0c = Counter('hello')c['z']0 (no KeyError!)Counter inherits from dict, so bracket access, .keys(), .values(), and .items() all work normally.
Five essential Counter features shown with code snippets and outputs. Notice that accessing a missing key returns 0 instead of raising a KeyError—a huge convenience over plain dicts.
Method / FeatureWhat It DoesReturn Type
Counter(iterable)Creates a Counter from any iterableCounter
.most_common(n)Returns the n most frequent items sorted by countlist of (item, count) tuples
.elements()Returns an iterator over elements, each repeated by its countiterator
.update(iterable)Adds counts from another iterable or Counter (does not replace)None
.subtract(iterable)Subtracts counts (allows negatives)None
counter[key]Returns count (0 for missing keys instead of KeyError)int

Common Counter methods and their behavior

SECTION 6

Worked Example — Analyzing a Sentence

Let's solve a complete problem: given the sentence "she sells sea shells by the sea shore", find the two most common letters (ignoring spaces) and the two most common words.

Letter & Word Frequency Analysis

  1. 1

    Step 1 — Store the Sentence

    Assign the sentence to a variable. We will use this variable for both letter and word counting. sentence = "she sells sea shells by the sea shore"
  2. 2

    Step 2 — Count Letters (Manual dict approach)

    First, remove spaces so we only count actual letters. Then use the dict.get() approach to build a frequency dictionary. letter_counts = {} for ch in sentence.replace(" ", ""): letter_counts[ch] = letter_counts.get(ch, 0) + 1
    {'s': 8, 'h': 4, 'e': 7, 'l': 4, 'a': 2, 'b': 1, 'y': 1, 't': 1, 'o': 1, 'r': 1}
  3. 3

    Step 3 — Find Top 2 Letters Using sorted()

    Sort the dictionary items by value in descending order and take the first two. top2 = sorted(letter_counts.items(), key=lambda x: x[1], reverse=True)[:2]
    Top 2 letters: [('s', 8), ('e', 7)]
  4. 4

    Step 4 — Count Words with Counter

    Split the sentence into words using .split(), then pass the resulting list to Counter. from collections import Counter word_counts = Counter(sentence.split())
    Counter({'sea': 2, 'she': 1, 'sells': 1, 'shells': 1, 'by': 1, 'the': 1, 'shore': 1})
  5. 5

    Step 5 — Find Top 2 Words with most_common()

    Counter's most_common() method makes this a single call—no need for sorted() or lambdas. word_counts.most_common(2)
    Top 2 words: [('sea', 2), ('she', 1)]
⚠

Counting Strings

Strings are iterable in Python. If you pass a string directly to Counter, it counts individual characters (including spaces). Use .split() first if you want to count words instead.

SECTION 7

Comparing Counting Approaches

Now that you have seen all three approaches, let's put them side by side. Each has trade-offs in terms of readability, flexibility, and how much code you need to write.

FeatureManual if/elsedict.get()Counter
Lines of code (loop body)4–5 lines2–3 lines1–2 lines
Readability for beginnersVery clear logicModerate—need to know get()Very high once you learn it
Built-in most_common()?No — must sort manuallyNo — must sort manuallyYes
Missing key behaviorKeyError if uncheckedReturns default valueReturns 0 automatically
Arithmetic on counts?Must write manuallyMust write manuallyYes — +, −, &, | operators
Requires import?NoNoYes (collections)

Side-by-side comparison of three frequency counting approaches

🎯

WHEN TO USE WHICH

Use the manual approach when you're learning how dictionaries work. Use dict.get() for quick scripts where you don't want to import anything. Use Counter for anything serious—data analysis, competitive programming, or any time you need most_common() or counter arithmetic. Think of it like choosing a calculator: you can do long division by hand, but a calculator is faster and less error-prone for real work.

SECTION 8

Connection to Advanced Tools

Once you are comfortable with Counter, you will naturally encounter related tools in Python's ecosystem. Understanding how Counter fits into the bigger picture will help you level up when you move to more advanced coursework or real-world projects.

ToolModuleRelationship to Counter
defaultdict(int)collectionsA dict that auto-creates missing keys with a default factory. Using int gives 0 for missing keys—very similar to manual counting but without get().
pandas.Series.value_counts()pandasCounter's big sibling in data science. Works on DataFrame columns and returns a sorted Series of frequencies with powerful filtering and grouping.
Bag / Multiset (math concept)—Counter is Python's implementation of the multiset data structure—a set that allows duplicate elements, tracked by count.
nltk.FreqDistnltkA frequency distribution class used in natural language processing. Built on top of Counter with extra plotting and analysis methods.

How Counter relates to more advanced tools

As you continue into AP Computer Science, data science, or competitive programming, you will find that frequency counting is everywhere. Mastering the manual dictionary pattern ensures you understand the fundamentals, while knowing Counter gives you a tool you can deploy instantly in any Python project.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain in your own words why a Python dictionary is a good data structure for counting frequencies. What property of dictionaries makes the lookup-and-update operation efficient?
Show answer
A dictionary maps unique keys to values, which naturally represents the relationship between an item and its count. Because dictionaries use hash tables internally, looking up whether a key exists and retrieving its value are both O(1) on average—meaning the time doesn't grow significantly even with millions of items. This makes the 'check if the item is already counted, then update' loop very fast. Lists, by contrast, would require searching through every element to find a match, making them much slower for this task.

Question 2: BASIC CALCULATION

Using the dict.get() pattern, write a function char_freq(s) that takes a string s and returns a dictionary of character frequencies. What does char_freq("banana") return?
Show answer
The function is: def char_freq(s): counts = {} for ch in s: counts[ch] = counts.get(ch, 0) + 1 return counts Calling char_freq("banana") returns {'b': 1, 'a': 3, 'n': 2}. The loop visits each character: 'b' (new → 1), 'a' (new → 1), 'n' (new → 1), 'a' (exists → 2), 'n' (exists → 2), 'a' (exists → 3). The get() method handles both new and existing keys in one expression.

Question 3: INTERMEDIATE

Given the list grades = ['A', 'B', 'A', 'C', 'B', 'A', 'D', 'B', 'C', 'A', 'B', 'A'], use collections.Counter to (a) count all grade frequencies, (b) find the 2 most common grades, and (c) determine how many students got a grade of 'F'.
Show answer
(a) from collections import Counter grade_counts = Counter(grades) gives Counter({'A': 5, 'B': 4, 'C': 2, 'D': 1}). (b) grade_counts.most_common(2) returns [('A', 5), ('B', 4)]. (c) grade_counts['F'] returns 0. This is a key advantage of Counter: accessing a missing key returns 0 instead of raising a KeyError. With a plain dict, you would need to use .get('F', 0) or an if-check to avoid a crash.

Question 4: APPLIED

You are building a simple spam detector. You have two strings: spam_words = "buy now free buy discount free free offer" and email = "Hey, check out this free offer to buy groceries". Use Counter on both word lists and the & (intersection) operator to find which spam words appear in the email and their minimum overlapping counts.
Show answer
First, create counters: from collections import Counter spam_c = Counter(spam_words.lower().split()) email_c = Counter(email.lower().split()). The spam counter is Counter({'free': 3, 'buy': 2, 'now': 1, 'discount': 1, 'offer': 1}) and the email counter includes {'free': 1, 'offer': 1, 'buy': 1, ...}. Using the intersection overlap = spam_c & email_c gives Counter({'free': 1, 'buy': 1, 'offer': 1}). The & operator takes the minimum count for each shared key, so even though spam had 'free' 3 times, the overlap is 1 because the email contains it only once. This technique is useful for measuring how much a message resembles a spam pattern.

Question 5: CRITICAL THINKING

Write a function are_anagrams(word1, word2) that returns True if two words are anagrams (contain exactly the same letters in possibly different order) and False otherwise. Implement it using Counter in a single comparison. Then explain why this approach works and what its time complexity is relative to the length of the words.
Show answer
The function is elegantly simple: from collections import Counter def are_anagrams(word1, word2): return Counter(word1.lower()) == Counter(word2.lower()) For example, are_anagrams('Listen', 'Silent') returns True because both produce Counter({'l': 1, 'i': 1, 's': 1, 't': 1, 'e': 1, 'n': 1}). This works because two words are anagrams if and only if they contain the same characters with the same frequencies—which is exactly what Counter equality checks. The time complexity is O(n + m) where n and m are the lengths of the two words, because Counter iterates through each string once to build the count, and dictionary comparison is O(k) where k is the number of unique keys. An alternative approach using sorted() would be O(n log n), making the Counter approach asymptotically faster for long strings.

SUMMARY

Lesson Summary

Summary

Frequency counting is the process of tallying how often each unique item appears in a collection. In Python, you can implement this with a manual if/else loop that checks whether a key exists before incrementing, or with the more concise dict.get(key, 0) + 1 pattern that collapses the check and update into one line. Both approaches build a standard dictionary mapping items to their counts.

For maximum convenience, Python's collections.Counter class handles counting in a single call and provides powerful extras: most_common(n) for top-N queries, arithmetic operators (+, −, &, |) for combining counters, and safe missing-key access that returns 0 instead of raising a KeyError. Whether you are analyzing text, processing survey data, or solving coding challenges, mastering both the manual dictionary approach and Counter will make you a versatile Python programmer.

Varsity Tutors • Python • Frequency Counting with Dicts