In Python, a collection is an object that contains a group of elements and allows you to work with them as a single unit.
Collections typically support:
- iteration (
for item in collection) - membership testing (
x in collection) - length determination (
len(collection)) - indexed or keyed access (if ordered or associative)
Python has no strict βcollection interfaceβ, but follows informal protocols. If an object supports
__iter__,__len__,__contains__, it can be considered a collection.
- What is NOT a collection
- Built-in collections
- Extended collections from the standard library
- Other collection-like types
- 1. Lists β
list - 2. Dictionaries β
dict - 3. Tuples β
tuple - 4. SimpleNamespace
- 5. Sets β
set - 6. Immutable sets β
frozenset - 7. namedtuple β named tuples
- 8. deque β double-ended queue
- 9. Counter β element counter
- 10. defaultdict β dictionary with defaults
- 11. dataclass β data classes
- 12. UserList β custom lists
- 13. UserDict β custom dictionaries
- π Memory and Performance Comparison
- π§ Performance Recommendations
- π Collection Comparison
- π‘ When to Use What?
What is NOT a collection
The following types are not collections, as they do not contain groups of elements:
int,float,boolβ scalar valuesNoneβ absence of value- functions, modules, classes β these are objects, but not data containers (unless they contain
__dict__)
Built-in collections
Available without imports:
| Type | Description |
|---|---|
list | Ordered, mutable sequence. |
tuple | Ordered, immutable sequence. |
dict | Ordered (since Python 3.7) key-value mapping. |
set | Unordered collection of unique elements. |
frozenset | Immutable version of set. |
Extended collections from the standard library
| Type | Module | Purpose |
|---|---|---|
SimpleNamespace | types | Object with dynamic attributes (dot-access alternative to dict). |
namedtuple | collections | Immutable tuple with named fields. |
deque | collections | Double-ended queue β efficient appends/pops from both ends. |
Counter | collections | Dictionary subclass for counting hashable objects. |
defaultdict | collections | Dictionary with default values for missing keys. |
dataclass | dataclasses | Auto-generates __init__, __repr__, __eq__, etc. |
UserList | collections | Base class for custom list-like objects. |
UserDict | collections | Base class for custom dict-like objects. |
Other collection-like types
Though not always called βcollectionsβ, these types also represent or store groups of data.
1. str β string
Immutable ordered sequence of characters.
s = "Python"
print(len(s)) # β 6
print(s[0]) # β P
print('y' in s) # β True
print(list(s)) # β ['P', 'y', 't', 'h', 'o', 'n']
2. bytes, bytearray
bytesβ immutable sequence of bytes.bytearrayβ mutable version.
b = b"hello"
print(b[0]) # β 104
print(len(b)) # β 5
ba = bytearray(b"hello")
ba[0] = 72
print(ba) # β bytearray(b'Hello')
3. range
Lazy ordered numeric sequence. Does not store elements in memory.
r = range(3)
print(list(r)) # β [0, 1, 2]
print(1 in r) # β True
print(r[2]) # β 2
4. array.array
Stores homogeneous numeric data compactly (like C arrays).
from array import array
arr = array('i', [1, 2, 3]) # 'i' = signed int
print(arr) # β array('i', [1, 2, 3])
5. Generators and iterators
Do not store data β generate on demand. Do not support len() or indexing.
gen = (x * 2 for x in range(3))
print(list(gen)) # β [0, 2, 4]
# len(gen) β TypeError
6. ChainMap (from collections)
Groups multiple dictionaries into a single view β lookup searches through maps in order.
from collections import ChainMap
d1 = {'a': 1}
d2 = {'b': 2}
cm = ChainMap(d1, d2)
print(cm['a']) # β 1
print(cm['b']) # β 2
7. OrderedDict (from collections)
Dictionary that remembers insertion order. Relevant for Python < 3.7.
from collections import OrderedDict
od = OrderedDict([('a', 1), ('b', 2)])
print(od) # β OrderedDict([('a', 1), ('b', 2)])
8. enum.Enum, enum.Flag
Collections of named constants.
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
print(list(Color)) # β [<Color.RED: 1>, <Color.GREEN: 2>]
9. typing.NamedTuple, typing.TypedDict
Type-annotated wrappers over namedtuple and dict.
from typing import NamedTuple, TypedDict
class Person(NamedTuple):
name: str
age: int
p = Person("Alice", 25)
class Movie(TypedDict):
title: str
year: int
m: Movie = {"title": "Matrix", "year": 1999}
10. heapq, bisect β tools, not collections
Work with collections but are not collections themselves:
heapqβ heap queue algorithm (priority queues via lists).bisectβ maintain sorted order in lists.
1. Lists β list
Ordered, mutable collection. Elements can repeat, any types allowed.
Used when you need a flexible sequence: adding, removing, modifying elements.
Creation: []
boris_list = ["Boris", "Moscow", 30, "engineer"]
print(f"List creation: {boris_list}")
# β List creation: ['Boris', 'Moscow', 30, 'engineer']
print(f"Element at index 0: {boris_list[0]}")
# β Element at index 0: Boris
boris_list[2] = 31
print(f"After modification: {boris_list}")
# β After modification: ['Boris', 'Moscow', 31, 'engineer']
boris_list.append("married")
print(f"After append: {boris_list}")
# β After append: ['Boris', 'Moscow', 31, 'engineer', 'married']
boris_list.insert(1, "Russia")
print(f"After insert: {boris_list}")
# β After insert: ['Boris', 'Russia', 'Moscow', 31, 'engineer', 'married']
boris_list.remove("engineer")
print(f"After remove by value: {boris_list}")
# β After remove by value: ['Boris', 'Russia', 'Moscow', 31, 'married']
del boris_list[2]
print(f"After delete by index: {boris_list}")
# β After delete by index: ['Boris', 'Russia', 31, 'married']
boris_list.extend(["hobby", "fishing"])
print(f"After extend: {boris_list}")
# β After extend: ['Boris', 'Russia', 31, 'married', 'hobby', 'fishing']
boris_list.pop()
print(f"After pop: {boris_list}")
# β After pop: ['Boris', 'Russia', 31, 'married', 'hobby']
2. Dictionaries β dict
Collection of key β value pairs. Keys must be hashable. Since Python 3.7, insertion order is preserved.
Useful for structured data: profiles, configs, JSON.
Creation: {}
alice_dict = {"name": "Alice", "age": 25, "city": "London", "occupation": "artist"}
print(f"Dict creation: {alice_dict}")
# β Dict creation: {'name': 'Alice', 'age': 25, 'city': 'London', 'occupation': 'artist'}
print(f"Value by key 'name': {alice_dict['name']}")
# β Value by key 'name': Alice
alice_dict["age"] = 26
print(f"After update: {alice_dict}")
# β After update: {'name': 'Alice', 'age': 26, 'city': 'London', 'occupation': 'artist'}
alice_dict["hobby"] = "painting"
print(f"After adding pair: {alice_dict}")
# β After adding pair: {'name': 'Alice', 'age': 26, 'city': 'London', 'occupation': 'artist', 'hobby': 'painting'}
del alice_dict["city"]
print(f"After deleting pair: {alice_dict}")
# β After deleting pair: {'name': 'Alice', 'age': 26, 'occupation': 'artist', 'hobby': 'painting'}
hobby = alice_dict.pop("hobby")
print(f"After pop: {alice_dict}, value: {hobby}")
# β After pop: {'name': 'Alice', 'age': 26, 'occupation': 'artist'}, value: painting
print(f"Key 'name' exists: {'name' in alice_dict}")
# β Key 'name' exists: True
3. Tuples β tuple
Ordered, immutable collection. Suitable for fixed data.
Used when immutability matters: coordinates, parameters, return values.
Creation: ()
boris_tuple = ("Boris", "Moscow", 30, "engineer")
print(f"Tuple creation: {boris_tuple}")
# β Tuple creation: ('Boris', 'Moscow', 30, 'engineer')
print(f"Element at index 2: {boris_tuple[2]}")
# β Element at index 2: 30
# boris_tuple[0] = "Ivan" β TypeError
# boris_tuple.append("something") β AttributeError
- Tuples use less memory and are faster than lists.
- Ideal when mutability is not needed.
4. SimpleNamespace
Simple class from types for creating objects with dynamic attributes. Access via dot notation (obj.attr).
Useful when you want obj.name syntax without defining a full class.
from types import SimpleNamespace
alice_ns = SimpleNamespace(name="Alice", age=25, city="London")
print(f"Object: {alice_ns}")
# β Object: namespace(name='Alice', age=25, city='London')
print(f"Name: {alice_ns.name}")
# β Name: Alice
alice_ns.age = 26
print(f"After update: {alice_ns}")
# β After update: namespace(name='Alice', age=26, city='London')
alice_ns.occupation = "artist"
print(f"With new attribute: {alice_ns}")
# β With new attribute: namespace(name='Alice', age=26, city='London', occupation='artist')
del alice_ns.city
print(f"After deletion: {alice_ns}")
# β After deletion: namespace(name='Alice', age=26, occupation='artist')
setattr(alice_ns, "hobby", "painting")
print(f"Via setattr: {alice_ns}")
# β Via setattr: namespace(name='Alice', age=26, occupation='artist', hobby='painting')
delattr(alice_ns, "hobby")
print(f"Via delattr: {alice_ns}")
# β Via delattr: namespace(name='Alice', age=26, occupation='artist')
- Alternative to dict when
obj.nameis preferable toobj['name'].
5. Sets β set
Unordered collection of unique elements. Supports set operations: union, intersection, difference.
Used for deduplication and membership testing.
Creation: {} or set()
numbers = {1, 2, 3, 3, 2, 1}
print(f"Set: {numbers}")
# β Set: {1, 2, 3}
numbers.add(4)
print(f"After add: {numbers}")
# β After add: {1, 2, 3, 4}
numbers.remove(2)
print(f"After remove: {numbers}")
# β After remove: {1, 3, 4}
other = {3, 4, 5}
print(f"Union: {numbers | other}")
# β Union: {1, 3, 4, 5}
print(f"Intersection: {numbers & other}")
# β Intersection: {3, 4}
print(f"Difference: {numbers - other}")
# β Difference: {1}
6. Immutable sets β frozenset
Immutable version of set. Can be used as dict keys or set elements.
frozen = frozenset([1, 2, 3, 2])
print(f"frozenset: {frozen}")
# β frozenset: frozenset({1, 2, 3})
other = frozenset([3, 4])
print(f"Intersection: {frozen & other}")
# β Intersection: frozenset({3})
print(f"Union: {frozen | other}")
# β Union: frozenset({1, 2, 3, 4})
# frozen.add(5) β AttributeError
7. namedtuple β named tuples
Immutable structure with named field access. More readable than plain tuples.
from collections import namedtuple
Person = namedtuple("Person", ["name", "age", "city"])
alice = Person("Alice", 25, "London")
print(f"Object: {alice}")
# β Object: Person(name='Alice', age=25, city='London')
print(f"Name: {alice.name}")
# β Name: Alice
print(f"Age: {alice[1]}")
# β Age: 25
# alice.age = 26 β AttributeError
alice_new = alice._replace(age=26)
print(f"Copy with update: {alice_new}")
# β Copy with update: Person(name='Alice', age=26, city='London')
- Ideal for records: points, users, configs β when immutability and readability matter.
8. deque β double-ended queue
Optimized for fast appends/pops at both ends. More efficient than list for appendleft, popleft.
from collections import deque
d = deque([1, 2, 3])
print(f"Initial deque: {d}")
# β Initial deque: deque([1, 2, 3])
d.appendleft(0)
print(f"After appendleft: {d}")
# β After appendleft: deque([0, 1, 2, 3])
d.append(4)
print(f"After append: {d}")
# β After append: deque([0, 1, 2, 3, 4])
left = d.popleft()
print(f"After popleft: {left}, remaining: {d}")
# β After popleft: 0, remaining: deque([1, 2, 3, 4])
right = d.pop()
print(f"After pop: {right}, remaining: {d}")
# β After pop: 4, remaining: deque([1, 2, 3])
- Used in algorithms: BFS, LRU caches, buffers β where end operations must be fast.
9. Counter β element counter
Counts frequency of elements in an iterable. Useful for statistics and analysis.
from collections import Counter
text = "abracadabra"
c = Counter(text)
print(f"Letter counts: {c}")
# β Letter counts: Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
print(f"Frequency of 'a': {c['a']}")
# β Frequency of 'a': 5
print(f"Top 3: {c.most_common(3)}")
# β Top 3: [('a', 5), ('b', 2), ('r', 2)]
c2 = Counter("bukva")
c.update(c2)
print(f"After update: {c}")
# β After update: Counter({'a': 6, 'b': 3, 'r': 2, 'c': 1, 'd': 1, 'u': 1, 'k': 1, 'v': 1})
- Useful for text analysis, logs, voting β anywhere you need to count βwhat appears most oftenβ.
10. defaultdict β dictionary with defaults
Automatically creates default values for missing keys. Eliminates if key in dict checks.
from collections import defaultdict
dd_list = defaultdict(list)
dd_list["fruits"].append("apple")
dd_list["fruits"].append("banana")
print(f"List: {dict(dd_list)}")
# β List: {'fruits': ['apple', 'banana']}
dd_int = defaultdict(int)
for char in "abracadabra":
dd_int[char] += 1
print(f"Counts: {dict(dd_int)}")
# β Counts: {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}
dd_set = defaultdict(set)
dd_set["cities"].add("Moscow")
dd_set["cities"].add("SPb")
print(f"Set: {dict(dd_set)}")
# β Set: {'cities': {'Moscow', 'SPb'}}
- Eliminates boilerplate like
if key not in d: d[key] = []. - Makes code cleaner and safer.
11. dataclass β data classes
Decorator that auto-generates __init__, __repr__, __eq__, etc.
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
city: str = "Not specified"
alice = Person("Alice", 25)
print(f"Object: {alice}")
# β Object: Person(name='Alice', age=25, city='Not specified')
print(f"Name: {alice.name}")
# β Name: Alice
alice.age = 26
print(f"After update: {alice}")
# β After update: Person(name='Alice', age=26, city='Not specified')
bob = Person("Bob", 30)
print(f"Alice == Bob: {alice == bob}")
# β Alice == Bob: False
@dataclass(frozen=True)
class ImmutablePerson:
name: str
age: int
ivan = ImmutablePerson("Ivan", 40)
# ivan.age = 41 β FrozenInstanceError
- Replaces manual
__init__,__repr__,__eq__. - Ideal for DTOs, configs, models.
12. UserList β custom lists
Inherits from collections.UserList. Used to create lists with custom behavior.
from collections import UserList
class LoggingList(UserList):
def append(self, item):
print(f"[LOG] Append: {item}")
super().append(item)
def remove(self, item):
print(f"[LOG] Remove: {item}")
super().remove(item)
log_list = LoggingList([1, 2, 3])
print(f"Initial: {log_list}")
# β Initial: [1, 2, 3]
log_list.append(4)
# β [LOG] Append: 4
print(f"After append: {log_list}")
# β After append: [1, 2, 3, 4]
log_list.remove(2)
# β [LOG] Remove: 2
print(f"After remove: {log_list}")
# β After remove: [1, 3, 4]
- Useful for adding logging, validation, or modifying standard list behavior.
13. UserDict β custom dictionaries
Inherits from collections.UserDict. Used to create dicts with custom behavior.
from collections import UserDict
class LowerKeyDict(UserDict):
def __setitem__(self, key, value):
key = key.lower() if isinstance(key, str) else key
super().__setitem__(key, value)
def __getitem__(self, key):
key = key.lower() if isinstance(key, str) else key
return super().__getitem__(key)
ld = LowerKeyDict()
ld["Name"] = "Alice"
print(f"Value by 'Name': {ld['Name']}")
# β Value by 'Name': Alice
print(f"Value by 'name': {ld['name']}")
# β Value by 'name': Alice
print(f"Keys: {list(ld.keys())}")
# β Keys: ['name']
- Used for key normalization, validation, logging, caching, etc.
π Memory and Performance Comparison
Collection choice impacts performance and memory usage. Below are practical benchmarks for common scenarios.
1. Memory: list vs tuple vs array.array
import sys
from array import array
n = 1_000_000
data = list(range(n))
data_t = tuple(range(n))
data_a = array('i', range(n))
print(f"list: {sys.getsizeof(data) / 1024 / 1024:.2f} MB")
# β list: 8.00 MB
print(f"tuple: {sys.getsizeof(data_t) / 1024 / 1024:.2f} MB")
# β tuple: 8.00 MB
print(f"array: {sys.getsizeof(data_a) / 1024 / 1024:.2f} MB")
# β array: 3.81 MB
array.arrayuses ~2x less memory for numbers.listandtupleconsume similar memory, buttupleis slightly faster for iteration.
2. Access speed: list vs tuple vs array.array
import time
def time_access(collection, name):
start = time.perf_counter()
total = 0
for i in range(len(collection)):
total += collection[i]
end = time.perf_counter()
print(f"{name}: {end - start:.4f} seconds")
n = 10_000_000
lst = list(range(n))
tpl = tuple(range(n))
arr = array('i', range(n))
time_access(lst, "list") # β list: 1.2000 seconds
time_access(tpl, "tuple") # β tuple: 1.0000 seconds
time_access(arr, "array") # β array: 0.8000 seconds
array.arrayis fastest for numeric data.tupleis 10β20% faster thanlist.- Difference is noticeable at scale.
3. Memory: dict vs SimpleNamespace vs dataclass
d = {"name": "A", "age": 25, "city": "X", "hobby": "Y", "job": "Z"}
ns = SimpleNamespace(name="A", age=25, city="X", hobby="Y", job="Z")
dc = PersonDC("A", 25, "X", "Y", "Z")
print(f"dict: {sys.getsizeof(d)} bytes") # β 232
print(f"SimpleNamespace: {sys.getsizeof(ns)} bytes") # β 64
print(f"dataclass: {sys.getsizeof(dc)} bytes") # β 64
print(f"ns.__dict__: {sys.getsizeof(ns.__dict__)} bytes") # β 232
SimpleNamespaceanddataclassuse same memory asdictdue to__dict__.- Use
__slots__for memory savings.
4. Memory optimization: dataclass with __slots__
@dataclass
class PersonSlots:
__slots__ = ("name", "age", "city", "hobby", "job")
name: str
age: int
city: str
hobby: str
job: str
dc_slots = PersonSlots("A", 25, "X", "Y", "Z")
print(f"dataclass + slots: {sys.getsizeof(dc_slots)} bytes")
# β 80 bytes
# dc_slots.new = "value" β AttributeError
__slots__saves memory and speeds up attribute access.- Trade-off: no dynamic attribute addition.
5. Lookup speed: list vs set
n = 1_000_000
lst = list(range(n))
st = set(range(n))
def time_in(collection, target, name):
start = time.perf_counter()
for _ in range(1000):
_ = target in collection
end = time.perf_counter()
print(f"{name} (search {target}): {end - start:.4f} seconds")
time_in(lst, 999_999, "list") # β 10.0000 seconds
time_in(st, 999_999, "set") # β 0.0005 seconds
setis thousands of times faster thanlistfor membership testing.- Always use
setfor frequentx in collectionchecks.
6. Memory: set vs frozenset
s = set(range(1000))
fs = frozenset(range(1000))
print(f"set: {sys.getsizeof(s)} bytes") # β 32792
print(f"frozenset: {sys.getsizeof(fs)} bytes") # β 32792
frozensetandsetuse identical memory.- Difference is only mutability.
7. Append speed: list.append vs deque.append vs deque.appendleft
from collections import deque
import time
def time_append(collection, n, method='append'):
start = time.perf_counter()
for i in range(n):
if method == 'appendleft' and hasattr(collection, 'appendleft'):
collection.appendleft(i)
else:
collection.append(i)
end = time.perf_counter()
return end - start
n = 100_000
lst = []
dq = deque()
time_list_append = time_append(lst, n) # β 0.0100 seconds
time_deque_append = time_append(dq, n) # β 0.0100 seconds
time_deque_appendleft = time_append(deque(), n, 'appendleft') # β 0.0100 seconds
# list.insert(0):
lst = []
start = time.perf_counter()
for i in range(n):
lst.insert(0, i)
end = time.perf_counter()
print(f"list.insert(0): {end - start:.4f} seconds") # β 5.0000 seconds
deque.appendleftis O(1), unlikelist.insert(0)which is O(n).- Use
dequefor frequent operations at both ends.
π§ Performance Recommendations
| Situation | Use | Reason |
|---|---|---|
| Storing numbers, memory critical | array.array | 2x less memory, faster access |
| Data is immutable | tuple | Faster than list, safer |
Frequent x in collection checks | set / frozenset | O(1) vs O(n) for list |
| Operations at both ends | deque | appendleft/popleft in O(1) |
| Structured data, memory critical | dataclass + __slots__ | No __dict__, less memory |
| Counting frequencies | Counter | Optimized for this task |
| Custom behavior | UserList / UserDict | Safe extension of built-ins |
π Collection Comparison
| Type | Ordered | Mutable | Unique Elements | Index Access | Duplicates |
|---|---|---|---|---|---|
list | β Yes | β Yes | β No | β Yes | β Yes |
tuple | β Yes | β No | β No | β Yes | β Yes |
dict | β Yes* | β Yes | Keys only | β No | Values: β |
set | β No | β Yes | β Yes | β No | β No |
frozenset | β No | β No | β Yes | β No | β No |
SimpleNamespace | β Yes (attrs) | β Yes | β No (attrs can repeat semantically) | β No | β Yes |
namedtuple | β Yes | β No | β No | β Yes | β Yes |
deque | β Yes | β Yes | β No | β Yes | β Yes |
Counter | β No | β Yes | β No | β No (but has keys) | β Yes |
defaultdict | β Yes* | β Yes | Keys only | β No | Values: β |
dataclass | β Yes (fields) | β Yes (if not frozen) | β No | β No | β Yes |
UserList | β Yes | β Yes | β No | β Yes | β Yes |
UserDict | β Yes* | β Yes | Keys only | β No | Values: β |
str | β Yes | β No | β No | β Yes | β Yes |
bytes | β Yes | β No | β No | β Yes | β Yes |
bytearray | β Yes | β Yes | β No | β Yes | β Yes |
range | β Yes | β No | β No | β Yes | β No |
array.array | β Yes | β Yes | β No | β Yes | β Yes |
ChainMap | β Yes* | β Yes | Keys only | β No | Values: β |
Enum | β Yes | β No | β Yes (members) | β No | β No |
- β since Python 3.7,
dict,defaultdict,UserDict,ChainMappreserve insertion order.
π‘ When to Use What?
| Task | Collection |
|---|---|
| Mutable sequence | list / deque / UserList / bytearray |
| Immutable data | tuple / namedtuple / frozenset / str / bytes |
| Fast lookup, deduplication | set / frozenset |
| Structured data | dict / dataclass / SimpleNamespace / UserDict / TypedDict |
| Use as dict key | frozenset |
| Dot-access temporary objects | SimpleNamespace / dataclass |
| Count frequencies | Counter |
| Default values for keys | defaultdict |
| Efficient end operations | deque |
| Custom list behavior | UserList |
| Custom dict behavior | UserDict |
| Binary data | bytes / bytearray / array.array |
| Configs with hierarchy | ChainMap |
| Named constants | Enum |
| Lazy sequences | range / generators |