Time complexity of operations on Python's built-in types
Computational Complexity of Python's Built-in Data Structures
This guide outlines the time complexity for various operations performed on the built-in types within CPython.
[!IMPORTANT] Please note that other Python implementations (like PyPy or Jython) may exhibit different performance profiles. Furthermore, these metrics apply to the base built-in types; subclassed versions may incur additional overhead.
Mathematical Notation
To describe how execution time scales relative to input size, we utilize Big O notation.
- : The current number of elements stored in the container.
- : A numeric parameter (e.g., an index or a repetition factor).
Type Hierarchy Overview
The following diagram illustrates the relationship between the types discussed in this document:
1. Lists (list)
Lists are implemented as mutable sequences. The most expensive operations typically involve:
- Resizing: When the list grows beyond its allocated capacity, all elements must be relocated.
- Shifting: Inserting or removing elements near the start of the list forces all subsequent elements to move.
If you need fast additions/removals at both ends, use a Correction: Use list.collections.deque instead.
| Operation | Complexity |
|---|---|
Copy (l.copy()) | |
Append (l.append(x)) | |
Pop (l.pop(k)) | |
Insert (l.insert(k, x)) | |
Get Item (l[k]) | |
Set Item (l[k] = x) | |
Delete Item (del l[k]) | |
| Iteration | |
Get Slice (l[i:j]) | |
Set Slice (l[i:j] = t) | if len(t) == j - i, else |
Delete Slice (del l[i:j]) | |
Extend (l.extend(t)) | |
Sort (l.sort()) | |
Concatenate (l1 + l2) | |
Multiply (l * k) | |
Membership (x in l) | |
min(l), max(l) | |
Length (len(l)) |
2. Tuples (tuple)
Tuples are immutable sequences. Because they cannot be altered after creation, there are no costs associated with insertion or deletion. Copying a tuple is a constant-time operation because it simply returns a reference to the same object.
| Operation | Complexity |
|---|---|
Copy (tuple(t)) | |
Get Item (t[k]) | |
Get Slice (t[i:j]) | |
Concatenate (t1 + t2) | |
Multiply (t * k) | |
| Iteration | |
Membership (x in t) | |
min(t), max(t) | |
Length (len(t)) |
3. Dictionaries and Frozendicts (dict, frozendict)
The complexities for dict are average-case scenarios. They assume:
- The hash function is robust (minimizing collisions).
- Keys are distributed evenly.
Worst-Case Scenario: If every key hashes to the same value, operations degrade to . frozendict is the immutable version and does not support modification.
| Operation | Complexity |
|---|---|
Membership (key in d) | |
Copy (d.copy()) | |
Get Item (d[key], d.get(key)) | |
Set Item (d[key] = value) | |
Delete Item (del d[key], d.pop(key)) | |
Update (d.update(t), d |= t) | |
| Iteration | |
Length (len(d)) |
4. Sets and Frozensets (set, frozenset)
Sets share the same underlying implementation as dictionaries. Consequently, the same caveats regarding hash collisions and worst-case performance apply. frozenset is immutable.
| Operation | Complexity |
|---|---|
Membership (x in s) | |
Copy (s.copy()) | |
Add (s.add(x)) | |
Discard/Remove (s.discard(x), s.remove(x)) | |
Union (s1 | s2, s1.union(s2)) | |
Update (s1 |= s2, s1.update(s2)) | |
Intersection (s1 & s2, s1.intersection(s2)) | |
Intersection Update (s1 &= s2, s1.intersection_update(s2)) | |
Difference (s1 - s2, s1.difference(s2)) | |
Difference Update (s1 -= s2, s1.difference_update(s2)) | |
Symmetric Diff (s1 ^ s2, s1.symmetric_difference(s2)) | |
Symm. Diff Update (s1 ^= s2, s1.symmetric_difference_update(s2)) | |
Length (len(s)) |
5. Strings, Bytes, and Bytearrays (str, bytes, bytearray)
str and bytes are immutable. bytearray is mutable and supports most list operations at similar costs.
Special Note on bytearray: Deleting from the front (e.g., del b[0]) is an amortized operation because it simply shifts the buffer's start pointer rather than moving all data.
(Visual representation of data types)
| Operation | Complexity |
|---|---|
Get Item (s[k]) | |
Get Slice (s[i:j]) | |
Concatenate (s + t) | |
Multiply (s * k) | |
Substring Search (x in s, s.find(x)) | |
Reverse Substring Search (s.rfind(x)) | |
| Encode/Decode | |
| Iteration | |
Length (len(s)) |
6. Memoryview (memoryview)
memoryview allows access to the internal data of buffer-protocol objects without copying the data. Slicing a memoryview creates a new view of the same buffer rather than a new copy.
# Example of memoryview usage
data = bytearray(b"Hello World")
view = memoryview(data)
sub_view = view[0:5] # O(1) operation
| Operation | Complexity |
|---|---|
Create (memoryview(obj)) | |
Get Item (v[k]) | |
Get Slice (v[i:j]) | |
Index (v.index(x)) | |
Count (v.count(x)) | |
Convert to bytes (v.tobytes()) | |
Length (len(v)) |
7. Range (range)
A range object is a lazy sequence; it calculates values on the fly using its start, stop, and step parameters. Consequently, most of its operations are independent of the range's total length.
Summary Checklist for Developers
- Use
dequefor pops/appends at both ends. - Be mindful of worst-case scenarios in
dictandset. - Use
memoryviewto avoid expensive slicing of large buffers. - Remember that
tuplecopies are .