Python

Count Word Occurrences in a Python List

Python · Published 2026-10-24

Rows of wooden printing letters stored in a type case
Wooden printing type, letter after letter, stored in a case.

To count one word, use words.count("apple"). To count every word at once, use collections.Counter(words), which returns a dictionary of word to frequency and gives you the top results with .most_common(n).

The examples below were run on Python 3.13 (and pandas 3.0 for the last section). Everything except the pandas part uses only the standard library.

Counting a single word: list.count

words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
print(words.count("apple"))
print(words.count("durian"))
3
0

list.count(x) scans the whole list and compares each element with ==. It is the simplest option when you need the frequency of one or two specific words. It returns 0 for missing items rather than raising.

Do not call it in a loop over every distinct word: each call is a full pass, so counting all k distinct words in a list of n items costs O(n × k). See the timing section below.

Counting every word: collections.Counter

Counter is a dict subclass built for exactly this job. It counts in a single pass.

from collections import Counter

words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
counts = Counter(words)
print(counts)
print(counts["apple"], counts["durian"])
print(counts.most_common(2))
print(dict(counts))
Counter({'apple': 3, 'banana': 2, 'cherry': 1})
3 0
[('apple', 3), ('banana', 2)]
{'apple': 3, 'banana': 2, 'cherry': 1}

Useful properties:

Updating and combining counters

Counters support incremental updates and multiset arithmetic, which is handy when you process data in batches, files or days:

from collections import Counter

monday = Counter(["apple", "banana", "apple"])
tuesday = Counter(["banana", "cherry", "banana"])

total = monday + tuesday
print(total)
print(monday - tuesday)        # drops zero/negative counts
print(monday & tuesday)        # min of each count
print(monday | tuesday)        # max of each count

monday.update(["cherry", "apple"])
print(monday)
monday.subtract(["apple"] * 5)
print(monday)
print(+monday)                 # strip non-positive
print(total.total())           # Python 3.10+
Counter({'banana': 3, 'apple': 2, 'cherry': 1})
Counter({'apple': 2})
Counter({'banana': 1})
Counter({'apple': 2, 'banana': 2, 'cherry': 1})
Counter({'apple': 3, 'banana': 1, 'cherry': 1})
Counter({'banana': 1, 'cherry': 1, 'apple': -2})
Counter({'banana': 1, 'cherry': 1})
6

Note the asymmetry: the - operator discards results that are zero or negative, while the subtract() method keeps them. update() adds counts, unlike dict.update(), which replaces values. total() sums all counts and was added in Python 3.10; on older versions use sum(counts.values()).

Counting with a plain dict or defaultdict

When you need custom logic inside the loop, or want to see what Counter does underneath, a manual loop is straightforward:

from collections import defaultdict

words = ["apple", "banana", "apple", "cherry", "banana", "apple"]

counts = {}
for w in words:
    counts[w] = counts.get(w, 0) + 1
print(counts)

dd = defaultdict(int)
for w in words:
    dd[w] += 1
print(dict(dd))

print(sorted(counts.items(), key=lambda kv: kv[1], reverse=True))
{'apple': 3, 'banana': 2, 'cherry': 1}
{'apple': 3, 'banana': 2, 'cherry': 1}
[('apple', 3), ('banana', 2), ('cherry', 1)]

dict.get(key, 0) supplies a default for unseen words; defaultdict(int) does the same automatically because int() returns 0. Both are single-pass and O(n). Unlike Counter, reading a missing key from a defaultdict inserts it, which can quietly grow the dictionary.

Normalizing case and punctuation

Real-world words rarely arrive clean. "Apple", "apple," and "APPLE!" are three different strings, so they are counted separately unless you normalize first:

import string
from collections import Counter

words = ["Apple", "apple,", "APPLE!", "banana", "Banana."]
table = str.maketrans("", "", string.punctuation)
normalized = [w.translate(table).casefold() for w in words]
print(normalized)
print(Counter(normalized))
['apple', 'apple', 'apple', 'banana', 'banana']
Counter({'apple': 3, 'banana': 2})

str.casefold() is a more aggressive version of lower() intended for case-insensitive comparison (it maps German ß to ss, for instance). string.punctuation covers ASCII punctuation only; curly quotes, em dashes and other Unicode punctuation need a regex or an extended translation table.

Counting words in a block of text

Splitting text on whitespace leaves punctuation attached and keeps case differences:

import re
from collections import Counter

text = """The quick brown fox jumps over the lazy dog.
The dog barks; the FOX runs. Isn't it the fox's den?"""

naive = Counter(text.split())
print(naive["the"], naive["The"], naive["fox"])

tokens = re.findall(r"[a-z0-9]+(?:'[a-z]+)?", text.lower())
counts = Counter(tokens)
print(counts.most_common(4))
print(counts["fox"], counts["fox's"])
3 2 1
[('the', 5), ('fox', 2), ('dog', 2), ('quick', 1)]
2 1

Lowercasing first and extracting tokens with re.findall gives far more meaningful counts. The pattern keeps contractions and possessives (isn't, fox's) as single tokens; adjust it to your needs, for example r"\w+" for a looser match that also handles non-ASCII letters. For linguistic accuracy (stemming, lemmatization, multilingual tokenization) use a dedicated NLP library.

Ignoring stop words or counting specific words

from collections import Counter

STOP = {"the", "a", "an", "of", "and", "to", "is"}
words = "the cost of the fix is a fraction of the cost of the outage".split()
print(Counter(w for w in words if w not in STOP).most_common(3))

targets = ["cost", "fix", "bug"]
c = Counter(words)
print({t: c[t] for t in targets})
[('cost', 2), ('fix', 1), ('fraction', 1)]
{'cost': 2, 'fix': 1, 'bug': 0}

Counter accepts any iterable, so a generator expression filters without building an intermediate list. Counting once and then looking up several targets is better than calling list.count for each.

pandas: value_counts

If your words are already in a pandas Series or DataFrame column, use Series.value_counts():

import pandas as pd

s = pd.Series(["apple", "banana", "apple", "cherry", "banana", "apple"])
print(s.value_counts())
print(s.value_counts(normalize=True).round(3))
apple     3
banana    2
cherry    1
Name: count, dtype: int64
apple     0.500
banana    0.333
cherry    0.167
Name: proportion, dtype: float64

Results are sorted by frequency, normalize=True returns proportions, and dropna=False includes missing values. Since pandas 2.0 the resulting Series is named count (or proportion). Importing pandas just to count a list is overkill; reach for it when the data is already tabular.

Performance and complexity

Counting a list of 100,000 words drawn from a vocabulary of 1,000 (best of five runs with timeit on Python 3.13 on the build container for this article; numbers are indicative):

ApproachComplexityTime
{w: words.count(w) for w in set(words)}O(n × k)703 ms
Counter(words)O(n)2.2 ms
defaultdict(int) loopO(n)3.8 ms

Counter is fastest because its counting loop for iterables is implemented in C. The list.count approach is hundreds of times slower here and gets worse as the vocabulary grows. Memory use is O(k) for the dictionary of distinct words. For text that does not fit in memory, feed Counter.update() line by line from the file.

Common pitfalls

Summary

Use list.count(word) for a one-off lookup and collections.Counter for everything else: it counts in one pass, returns 0 for unseen words, ranks results with most_common(), and supports update() and arithmetic for combining batches. Normalize case and punctuation before counting, tokenize raw text with re.findall, and use pandas value_counts() when the data already lives in a DataFrame.