The `collections` module provides specialised container types that solve common problems more cleanly than plain dicts and lists.
**Counter — count occurrences**
```python
from collections import Counter
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
c = Counter(words)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})
c['apple'] # 3
c['mango'] # 0 — missing keys return 0, not KeyError
c.most_common(2) # [('apple', 3), ('banana', 2)]
c.total() # 6 (Python 3.10+)
# Count characters in a string
Counter('mississippi') # Counter({'s': 4, 'i': 4, 'p': 2, 'm': 1})
```
**Counter arithmetic**
```python
a = Counter({'cat': 3, 'dog': 2})
b = Counter({'dog': 1, 'bird': 2})
a + b # Counter({'cat': 3, 'dog': 3, 'bird': 2}) — union (add counts)
a - b # Counter({'cat': 3, 'dog': 1}) — subtract (drop negatives)
a & b # Counter({'dog': 1}) — intersection (min counts)
a | b # Counter({'cat': 3, 'bird': 2, 'dog': 2}) — union (max counts)
```
**defaultdict — automatic default values**
A `defaultdict` calls a factory function to create missing values instead of raising `KeyError`:
```python
from collections import defaultdict
# Group words by first letter
by_letter = defaultdict(list) # factory: list -> default is []
for word in ['apple', 'avocado', 'banana', 'blueberry']:
by_letter[word[0]].append(word)
# defaultdict({'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry']})
# Count without Counter
freq = defaultdict(int) # factory: int -> default is 0
for ch in 'hello world':
freq[ch] += 1
# Nested dicts
graph = defaultdict(set) # adjacency list for a graph
graph['A'].add('B')
graph['A'].add('C')
```
**defaultdict vs dict.get vs dict.setdefault**
```python
d = {}
# Plain dict — three approaches to missing keys:
d.get('key', 0) + 1 # read with default, but doesn't store it
d.setdefault('key', []).append(1) # store default on first access
# defaultdict — cleanest when all missing keys share the same default type
dd = defaultdict(list)
dd['key'].append(1) # no special handling needed
```
**deque — double-ended queue**
`deque` (pronounced 'deck') supports O(1) appends and pops from both ends. A regular list has O(n) `insert(0, x)` and `pop(0)` because everything shifts.
```python
from collections import deque
d = deque([1, 2, 3])
d.append(4) # [1, 2, 3, 4] — add to right
d.appendleft(0) # [0, 1, 2, 3, 4] — add to left, O(1)
d.pop() # 4, d = [0, 1, 2, 3]
d.popleft() # 0, d = [1, 2, 3]
d.rotate(1) # [3, 1, 2] — rotate right by 1
d.rotate(-1) # [1, 2, 3] — rotate left by 1
```
**maxlen — sliding window / fixed-size buffer**
When a `deque` has `maxlen`, new items automatically displace old ones from the other end:
```python
# Keep the last 5 commands typed
history = deque(maxlen=5)
for cmd in ['ls', 'cd /tmp', 'cat file', 'pwd', 'ls -la', 'whoami']:
history.append(cmd)
list(history) # ['cd /tmp', 'cat file', 'pwd', 'ls -la', 'whoami']
# 'ls' was dropped when 'whoami' arrived because maxlen=5
# Sliding window average
window = deque(maxlen=3)
for reading in [10, 20, 30, 40, 50]:
window.append(reading)
print(sum(window) / len(window)) # 10.0, 15.0, 20.0, 30.0, 40.0
```
**namedtuple — lightweight record type**
```python
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
p.x # 3 — attribute access
p[0] # 3 — index access still works (it's a tuple)
p.x, p.y # 3, 4
x, y = p # unpacking still works
```
**Useful namedtuple methods**
```python
Point._fields # ('x', 'y')
Point._make([3, 4]) # Point(x=3, y=4) from any iterable
p._replace(x=10) # Point(x=10, y=4) — returns a new instance
p._asdict() # {'x': 3, 'y': 4}
```
**namedtuple vs dict vs dataclass**
```
namedtuple — immutable, tuple-compatible, very memory-efficient
dict — mutable, flexible keys, slightly more memory
dataclass — mutable by default, supports methods, type hints, __post_init__
```
If the fields are fixed and you don't need mutation, `namedtuple` is the lightest choice.
OrderedDict, ChainMap, and choosing the right collection
**OrderedDict — dict that remembers insertion order**
In Python 3.7+, regular `dict` also preserves insertion order, so `OrderedDict` is rarely needed. It's still useful for its `.move_to_end()` method and for when you explicitly want to signal ordering matters:
```python
from collections import OrderedDict
od = OrderedDict()
od['a'] = 1
od['b'] = 2
od['c'] = 3
od.move_to_end('a') # move 'a' to the end
list(od) # ['b', 'c', 'a']
od.move_to_end('c', last=False) # move 'c' to the front
list(od) # ['c', 'b', 'a']
# LRU cache (evict least recently used)
class LRUCache:
def __init__(self, capacity):
self.cache = OrderedDict()
self.cap = capacity
def get(self, key):
if key not in self.cache: return -1
self.cache.move_to_end(key) # mark as recently used
return self.cache[key]
def put(self, key, value):
self.cache[key] = value
self.cache.move_to_end(key)
if len(self.cache) > self.cap:
self.cache.popitem(last=False) # evict oldest
```
**Choosing the right collection**
```
Need Use
──────────────────────────────────── ─────────────────────
Count occurrences Counter
Group items, auto-init missing keys defaultdict(list)
Accumulate, no KeyError on new keys defaultdict(int/float)
Fast queue (both ends) deque
Fixed-size sliding window deque(maxlen=N)
Lightweight immutable record namedtuple
Mutable record with methods dataclass
Dict with explicit ordering control OrderedDict
Everything else dict / list
```
**ChainMap — overlay multiple dicts without copying**
```python
from collections import ChainMap
defaults = {'color': 'red', 'size': 'M'}
overrides = {'color': 'blue'}
merged = ChainMap(overrides, defaults)
merged['color'] # 'blue' — found in overrides first
merged['size'] # 'M' — falls through to defaults
# Writes go to the first map
merged['weight'] = 'heavy'
overrides # {'color': 'blue', 'weight': 'heavy'}
```
Useful for config layering (user settings override defaults) and for scoped variable lookups.
Group a list of words by their first letter using `defaultdict`. Return a dict where each key is a letter and the value is a list of words.
from collections import defaultdict
def group_by_letter(words):
# your code here
pass
words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry']
result = group_by_letter(words)
print(result['a']) # ['apple', 'avocado']
print(result['b']) # ['banana', 'blueberry']
Solution
from collections import defaultdict
def group_by_letter(words):
groups = defaultdict(list)
for word in words:
groups[word[0]].append(word)
return dict(groups)
Use a `deque` to implement a sliding window maximum: given a list of numbers and window size k, return a list of the maximum value in each window.
from collections import deque
def sliding_max(nums, k):
# your code here
pass
print(sliding_max([1, 3, -1, -3, 5, 3, 6, 7], 3))
# [3, 3, 5, 5, 6, 7]
Solution
from collections import deque
def sliding_max(nums, k):
result = []
window = deque() # stores indices
for i, n in enumerate(nums):
while window and nums[window[-1]] <= n:
window.pop()
window.append(i)
if window[0] <= i - k:
window.popleft()
if i >= k - 1:
result.append(nums[window[0]])
return result
Use `namedtuple` to create a `Point` type with `x` and `y` fields. Return a function that computes the distance between two Points.
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
def distance(p1, p2):
# your code here
pass
a = Point(0, 0)
b = Point(3, 4)
print(distance(a, b)) # 5.0
Solution
from collections import namedtuple
import math
Point = namedtuple('Point', ['x', 'y'])
def distance(p1, p2):
return math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2)
Use `deque` with `maxlen` to keep only the last N items added to a stream. Implement a `StreamBuffer` class with `push(item)` and `get_recent()` methods.
from collections import deque
class StreamBuffer:
def __init__(self, maxlen):
# your code here
pass
def push(self, item):
# your code here
pass
def get_recent(self):
return list(self.buffer)
buf = StreamBuffer(3)
for x in [1, 2, 3, 4, 5]:
buf.push(x)
print(buf.get_recent()) # [3, 4, 5]
Solution
from collections import deque
class StreamBuffer:
def __init__(self, maxlen):
self.buffer = deque(maxlen=maxlen)
def push(self, item):
self.buffer.append(item)
def get_recent(self):
return list(self.buffer)
from collections import defaultdict
def totals_by_category(transactions):
totals = defaultdict(float)
for t in transactions:
totals[t['category']] += t['amount']
return dict(totals)
No split tab
Cookie preferences
We use necessary cookies to run the site. With your permission, we can also save your site preferences and use analytics and advertising cookies to understand usage and support the project.
Open tools in tabs.Exercises, IDE tools, and trainers stay available as site tabs.
Switch without losing context.Move between explanations, code, and utilities while keeping your place.
Use the sidebar as your map.The left panels hold navigation, settings, files, libraries, and tool controls.
PythonJavaScriptSQLite
One IDE, three practical modes
Python in the browser.Run small scripts, try libraries, and practice API requests without installing anything.
JavaScript for quick experiments.Test browser-friendly code and compare ideas next to your learning materials.
SQLite for data practice.Open the database explorer to inspect tables, write queries, and learn SQL workflows locally.
TopicIDE
Work side by side with split tabs
Keep instructions visible.Open an exercise or reference page beside the IDE instead of jumping back and forth.
Compare tools while you learn.Place regex checks, explanations, and code experiments next to each other when the task needs it.
Close the split when you are done.The workspace returns to a single focused tab, and your open site tabs remain available.
Code Typing Trainer
Or plain text
This trainer is designed for a physical keyboard.Open this section on a laptop or desktop with a wide screen. Touch typing practice will not work correctly on a phone.
Speed: 0 chars/min
0 words/min
Best speed (60s): 0 chars/min
0 words/min
Errors: 0
Total time: 0.0 s
To practice touch typing, avoid looking at your physical keyboard.