Skip to content

Collections Deep Dive — Part 2: deque, namedtuple, ChainMap

← Part 1: defaultdict, Counter, OrderedDict · Back to Overview

Learn Your Way

Read Build Watch Test Review Visualize
You are here Projects Flashcards

This part covers the remaining collections types: deque for fast double-ended operations, namedtuple for lightweight immutable records, and ChainMap for layered dict lookups.

deque — double-ended queue

A deque (pronounced "deck") is like a list, but optimized for adding and removing items from both ends:

from collections import deque

# Create a deque:
d = deque([1, 2, 3])

# Add to both ends (O(1) — fast):
d.append(4)        # [1, 2, 3, 4]
d.appendleft(0)    # [0, 1, 2, 3, 4]

# Remove from both ends (O(1) — fast):
d.pop()            # 4, deque is [0, 1, 2, 3]
d.popleft()        # 0, deque is [1, 2, 3]

# Rotate:
d = deque([1, 2, 3, 4, 5])
d.rotate(2)        # [4, 5, 1, 2, 3] — rotate right
d.rotate(-2)       # [1, 2, 3, 4, 5] — rotate left

list.insert(0, x) is O(n) because it shifts every element. deque.appendleft(x) is O(1). Use a deque when you need to frequently add or remove from the front.

Fixed-size buffer:

# Keep only the last 5 items:
recent = deque(maxlen=5)
for i in range(10):
    recent.append(i)
print(recent)    # deque([5, 6, 7, 8, 9], maxlen=5)

namedtuple — lightweight immutable objects

A namedtuple is like a tuple, but with named fields. Great for simple data containers:

from collections import namedtuple

# Define a type:
Point = namedtuple("Point", ["x", "y"])

# Create instances:
p = Point(3, 4)
print(p.x)       # 3
print(p.y)       # 4
print(p)          # Point(x=3, y=4)

# Still works like a tuple:
x, y = p         # Unpacking
print(p[0])       # 3 (indexing)

Real-world example:

User = namedtuple("User", ["name", "email", "role"])

alice = User("Alice", "alice@example.com", "admin")
bob = User("Bob", "bob@example.com", "user")

print(alice.name)    # "Alice"
print(bob.role)      # "user"

# Immutable — you cannot change fields:
alice.name = "Alicia"    # AttributeError!

# Create a modified copy with _replace:
alicia = alice._replace(name="Alicia")

For mutable named fields or more features, use dataclasses instead. See Dataclasses Explained.

ChainMap — search multiple dicts as one

A ChainMap groups multiple dictionaries together. Lookups search each dict in order until the key is found:

from collections import ChainMap

defaults = {"color": "blue", "size": "medium", "font": "Arial"}
user_prefs = {"color": "red"}
cli_args = {"size": "large"}

# Search CLI args first, then user prefs, then defaults:
config = ChainMap(cli_args, user_prefs, defaults)

print(config["size"])     # "large" (from cli_args)
print(config["color"])    # "red" (from user_prefs)
print(config["font"])     # "Arial" (from defaults)

This is useful for configuration systems where you have multiple layers of settings (defaults, user config, command-line overrides).

Quick reference

Type Use when you need... Example
Counter Count occurrences Word frequency, vote tallying
defaultdict Dict with automatic defaults Grouping, counting, nested dicts
namedtuple Immutable record with named fields Coordinates, database rows, config
deque Fast append/pop from both ends Queues, buffers, sliding windows
OrderedDict Order-sensitive equality Config where order matters
ChainMap Layered dict lookups Multi-level configuration

Common Mistakes

Mutating a namedtuple (you cannot):

Point = namedtuple("Point", ["x", "y"])
p = Point(1, 2)
p.x = 3    # AttributeError!
# Use p._replace(x=3) to create a new instance


← Part 1: defaultdict, Counter, OrderedDict Overview