Skip to content

Python collections — Counter, defaultdict, namedtuple, deque

The collectionscollections module provides specialized container types that go beyond the built-in listlist, dictdict, tupletuple, and setset. They make common tasks — counting, grouping, fixed-field records, and fast queues — shorter and faster.

overview.py
from collections import Counter, defaultdict, namedtuple, deque
 
print(Counter("banana"))                  # Counter({'a': 3, 'n': 2, 'b': 1})
print(defaultdict(list))                  # defaultdict(<class 'list'>, {})
Point = namedtuple("Point", "x y")
print(Point(1, 2))                         # Point(x=1, y=2)
print(deque([1, 2, 3]))                    # deque([1, 2, 3])
overview.py
from collections import Counter, defaultdict, namedtuple, deque
 
print(Counter("banana"))                  # Counter({'a': 3, 'n': 2, 'b': 1})
print(defaultdict(list))                  # defaultdict(<class 'list'>, {})
Point = namedtuple("Point", "x y")
print(Point(1, 2))                         # Point(x=1, y=2)
print(deque([1, 2, 3]))                    # deque([1, 2, 3])
TypeUse it for
CounterCounterCounting hashable items (tallies, frequencies).
defaultdictdefaultdictA dict that auto-creates default values for missing keys.
namedtuplenamedtupleLightweight, immutable records with named fields.
dequedequeFast appends/pops at both ends (queues, stacks).
OrderedDictOrderedDictA dict with extra order-related methods.
ChainMapChainMapSearch multiple dicts as one.

Counter

CounterCounter is a dictdict subclass for counting. Pass any iterable and it tallies occurrences.

counter.py
from collections import Counter
 
votes = ["a", "b", "a", "c", "a", "b"]
c = Counter(votes)
print(c)                  # Counter({'a': 3, 'b': 2, 'c': 1})
print(c["a"])             # 3
print(c["z"])             # 0  (missing keys return 0, never KeyError)
 
# Most common items
print(c.most_common(2))   # [('a', 3), ('b', 2)]
 
# Counters do arithmetic
c2 = Counter("aab")
print(c + c2)             # combine counts
print(c - c2)             # subtract counts
 
# Update with more data
c.update(["a", "d"])
print(c["a"], c["d"])     # 4 1
counter.py
from collections import Counter
 
votes = ["a", "b", "a", "c", "a", "b"]
c = Counter(votes)
print(c)                  # Counter({'a': 3, 'b': 2, 'c': 1})
print(c["a"])             # 3
print(c["z"])             # 0  (missing keys return 0, never KeyError)
 
# Most common items
print(c.most_common(2))   # [('a', 3), ('b', 2)]
 
# Counters do arithmetic
c2 = Counter("aab")
print(c + c2)             # combine counts
print(c - c2)             # subtract counts
 
# Update with more data
c.update(["a", "d"])
print(c["a"], c["d"])     # 4 1
MethodReturns
c.most_common(n)c.most_common(n)The nn highest-count (item, count)(item, count) pairs.
c.elements()c.elements()An iterator repeating each element by its count.
c.update(iterable)c.update(iterable)Add counts from another iterable/Counter.
c.total()c.total()Sum of all counts (Python 3.10+).

defaultdict

A regular dict raises KeyErrorKeyError for a missing key. A defaultdictdefaultdict instead calls a factory function to create a default value automatically — perfect for grouping and accumulating.

defaultdict.py
from collections import defaultdict
 
# Group words by their first letter
words = ["apple", "banana", "avocado", "cherry", "blueberry"]
groups = defaultdict(list)
for w in words:
    groups[w[0]].append(w)
print(dict(groups))
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
 
# Count with an int factory (default 0)
counts = defaultdict(int)
for ch in "mississippi":
    counts[ch] += 1
print(dict(counts))   # {'m': 1, 'i': 4, 's': 4, 'p': 2}
defaultdict.py
from collections import defaultdict
 
# Group words by their first letter
words = ["apple", "banana", "avocado", "cherry", "blueberry"]
groups = defaultdict(list)
for w in words:
    groups[w[0]].append(w)
print(dict(groups))
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
 
# Count with an int factory (default 0)
counts = defaultdict(int)
for ch in "mississippi":
    counts[ch] += 1
print(dict(counts))   # {'m': 1, 'i': 4, 's': 4, 'p': 2}

The factory can be listlist, intint, setset, dictdict, or any zero-argument callable.

namedtuple

namedtuplenamedtuple creates a tuple subclass with named fields — readable, immutable records without writing a full class.

namedtuple.py
from collections import namedtuple
 
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
 
print(p.x, p.y)          # 3 4   (access by name)
print(p[0], p[1])        # 3 4   (still a tuple — index access works)
 
# Unpacking works too
x, y = p
print(x, y)              # 3 4
 
# Handy helpers
print(p._asdict())       # {'x': 3, 'y': 4}
p2 = p._replace(y=10)    # returns a NEW point (immutable)
print(p2)                # Point(x=3, y=10)
namedtuple.py
from collections import namedtuple
 
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
 
print(p.x, p.y)          # 3 4   (access by name)
print(p[0], p[1])        # 3 4   (still a tuple — index access works)
 
# Unpacking works too
x, y = p
print(x, y)              # 3 4
 
# Handy helpers
print(p._asdict())       # {'x': 3, 'y': 4}
p2 = p._replace(y=10)    # returns a NEW point (immutable)
print(p2)                # Point(x=3, y=10)

Use a namedtuple when you have a small fixed set of fields and want clarity over a bare tuple or dict.

deque

A dequedeque (“double-ended queue”) supports O(1) appends and pops from both ends — unlike a list, which is slow at the front.

deque.py
from collections import deque
 
dq = deque([1, 2, 3])
dq.append(4)         # add right -> deque([1, 2, 3, 4])
dq.appendleft(0)     # add left  -> deque([0, 1, 2, 3, 4])
print(dq.pop())      # 4  (remove right)
print(dq.popleft())  # 0  (remove left)
print(dq)            # deque([1, 2, 3])
 
# Fixed-size deque: old items drop off automatically
last3 = deque(maxlen=3)
for i in range(6):
    last3.append(i)
print(last3)         # deque([3, 4, 5], maxlen=3)
 
# Rotate
d = deque([1, 2, 3, 4])
d.rotate(1)          # deque([4, 1, 2, 3])
print(d)
deque.py
from collections import deque
 
dq = deque([1, 2, 3])
dq.append(4)         # add right -> deque([1, 2, 3, 4])
dq.appendleft(0)     # add left  -> deque([0, 1, 2, 3, 4])
print(dq.pop())      # 4  (remove right)
print(dq.popleft())  # 0  (remove left)
print(dq)            # deque([1, 2, 3])
 
# Fixed-size deque: old items drop off automatically
last3 = deque(maxlen=3)
for i in range(6):
    last3.append(i)
print(last3)         # deque([3, 4, 5], maxlen=3)
 
# Rotate
d = deque([1, 2, 3, 4])
d.rotate(1)          # deque([4, 1, 2, 3])
print(d)
MethodEffect
append(x)append(x) / appendleft(x)appendleft(x)Add to the right / left.
pop()pop() / popleft()popleft()Remove from the right / left.
extend(it)extend(it) / extendleft(it)extendleft(it)Add many to the right / left.
rotate(n)rotate(n)Rotate items nn steps to the right.
maxlenmaxlenOptional fixed length; overflow drops the opposite end.

OrderedDict and ChainMap (briefly)

others.py
from collections import OrderedDict, ChainMap
 
# OrderedDict: insertion order is preserved (regular dicts do this too since 3.7),
# but it adds move_to_end() and an order-sensitive ==.
od = OrderedDict([("a", 1), ("b", 2)])
od.move_to_end("a")
print(list(od))             # ['b', 'a']
 
# ChainMap: search several dicts as one, first match wins.
defaults = {"color": "red", "size": "M"}
overrides = {"color": "blue"}
settings = ChainMap(overrides, defaults)
print(settings["color"])    # blue  (overrides win)
print(settings["size"])     # M     (falls back to defaults)
others.py
from collections import OrderedDict, ChainMap
 
# OrderedDict: insertion order is preserved (regular dicts do this too since 3.7),
# but it adds move_to_end() and an order-sensitive ==.
od = OrderedDict([("a", 1), ("b", 2)])
od.move_to_end("a")
print(list(od))             # ['b', 'a']
 
# ChainMap: search several dicts as one, first match wins.
defaults = {"color": "red", "size": "M"}
overrides = {"color": "blue"}
settings = ChainMap(overrides, defaults)
print(settings["color"])    # blue  (overrides win)
print(settings["size"])     # M     (falls back to defaults)

Common pitfalls

  • defaultdictdefaultdict creates keys on access — even just reading d[missing]d[missing] inserts it. Use d.get(k)d.get(k) if you don’t want that.
  • namedtuples are immutable_replace_replace returns a new instance; it doesn’t mutate.
  • Use dequedeque for queues, not list.pop(0)list.pop(0), which is O(n).
  • CounterCounter values can go negative after subtraction; use +c+c to drop non-positive counts.

Practice Exercises

Exercise 1 – Count characters

Exercise 2 – Group numbers by even/odd

Exercise 3 – Keep the last 3 items

Summary

  • CounterCounter tallies items and ranks them with most_commonmost_common.
  • defaultdictdefaultdict removes KeyErrorKeyError boilerplate for grouping/accumulating.
  • namedtuplenamedtuple gives readable, immutable records with named fields.
  • dequedeque is the right tool for fast double-ended queues and bounded buffers.
  • OrderedDictOrderedDict and ChainMapChainMap cover order-sensitive and layered-lookup needs.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did