Python Module 5 – Built-in data structures: lists, dicts, sets and more
Choosing the right data structure
Pick a list, tuple, dict, set, deque, heap or sorted list from the operations your program repeats, compare their costs, and check the choice with timeit.
What you will learn
- Compare the cost of common operations on list, dict, set and deque
- Choose a structure from the operations a problem needs
- Measure instead of guessing with timeit
- Detect a membership test that makes a loop quadratic, and fix it
- Estimate the memory of a container with sys.getsizeof()
Before you start
What you’ll learn · Before you start
What you will learn
- Compare the cost of common operations on list, dict, set and deque
- Choose a structure from the operations a problem needs
- Measure instead of guessing with timeit
- Detect a membership test that makes a loop quadratic, and fix it
- Estimate the memory of a container with sys.getsizeof()
Before you start
On this page
Asking whether a value is in a list of 100,000 items took about 2.7 ms in the run recorded below, and asking a set the same question took 16 ns. So choose a data structure by the operations your program repeats most, not by habit. Write down what happens to the data again and again: is it looked up by a key, checked for membership, taken from the front, kept in order, or read by position? Then pick the structure for which those operations are cheap: a dict for lookups by key, a set for “have I seen this?”, a deque for queues, a heap: A list kept in heap order: the item at index k is no larger than the items at 2k + 1 and 2k + 2, its children. The smallest item is then at index 0, and the rest of the list is only partly sorted. More in “heapq, bisect and array for efficient data” for “the smallest next”, a sorted list with bisect for ordered data and ranges, and a list (or a tuple, if it never changes) for everything read by position.
A quick measurement with timeit settles any doubt. Below, the structures of this module stand side by side, with
their costs, two measured traps and their memory.
Start from the operations your program repeats
The questions below put the structures in an order that settles most cases. Ask them from the top and stop at the first “yes”:
A first choice of structure from the operation a program repeats most
Text description of the diagram
The diagram is one column of five questions, read from the top. Each box names the structure to use when the answer is yes; a "no" leads down to the next question.
- Do you always take the smallest or most urgent item next? Yes: heapq on a list.
- Do you need sorted order or questions about ranges? Yes: a sorted list searched with bisect.
- Do you take items from the front? Yes: collections.deque.
- Do you look values up by a key? Yes: dict.
- Do you only ask whether an item was seen before? Yes: set.
When every answer is no, a list is the right start, or a tuple if the collection never changes.
The same rules fit into a small function, which also shows how to keep such a decision honest: write the cases down as tests.
def choose(needs):
"""The structure to start from, given the operations a program needs most often."""
if "smallest next" in needs:
return "heapq on a list"
if "in order" in needs or "range" in needs:
return "sorted list with bisect"
if "take from front" in needs:
return "collections.deque"
if "look up by key" in needs:
return "dict"
if "seen before?" in needs:
return "set"
if "dict key" in needs:
return "tuple"
return "list"
# Six programs and the operations each one repeats most:
scenarios = [
("print queue, jobs leave from the front", {"add at back", "take from front"}, "collections.deque"),
("student records by roll number", {"look up by key", "update"}, "dict"),
("unique visitors of a page", {"seen before?", "count"}, "set"),
("leaderboard shown in score order", {"add", "in order", "range"}, "sorted list with bisect"),
("next train by departure time", {"add", "smallest next"}, "heapq on a list"),
("map cell as a dictionary key", {"dict key"}, "tuple"),
]
for program, needs, expected in scenarios:
picked = choose(needs)
assert picked == expected, (program, picked)
print(f"{program:38} -> {picked}")
print("all", len(scenarios), "checks passed") Output
print queue, jobs leave from the front -> collections.deque student records by roll number -> dict unique visitors of a page -> set leaderboard shown in score order -> sorted list with bisect next train by departure time -> heapq on a list map cell as a dictionary key -> tuple all 6 checks passed
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 chooser.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The order of the questions matters. A leaderboard also looks scores up, but what it repeats most is showing them in
order, so the sorted list wins over a dict. A program with two equally frequent needs often uses two
structures: a dict to find a ticket by its id, plus a heap to find the most urgent one. Keeping two structures in
step costs a little code, and it is still much cheaper than scanning one structure that suits neither need. Press
Edit, add a seventh program to scenarios with the operations you think it repeats, and run it: the assert tells
you at once whether choose() agrees with you.
Checkpoint
Check: Choosing by the repeated operations
Each structure’s costs follow from how it is built
The costs come from how CPython builds each structure:
- A list is an array of references in one block of memory, with spare room at the end. Reading or replacing
a[i]costs the same for anyi, andappend()is cheap because the spare room means the array only rarely has to grow, so its cost is amortised cost: The cost of an operation averaged over a long run of them. list.append() is amortised O(1): most appends use spare room at the end of the list, and the rare one that grows the array is paid for by the many before it. O(1). Inserting or deleting near the front moves everything behind that position. - A dict is a hash table: A table of slots in which each key is kept at a place worked out from its hash, so a lookup looks in one slot or a few instead of comparing every key. Python's dict and set are hash tables. More in “Dictionaries: keys, values and views”:
hash(key)decides where an entry lives, so finding a key does not depend on how many others there are. A set works the same way, with keys and no values. These are average costs, which assume that keys rarely share a hash value; if every key hashed to the same value, each operation would degrade to O(n). - A deque is built for both ends: adding or removing there costs the same at any length, while reaching the middle by index costs O(n).
| Operation | Time | Extra space | Note |
|---|---|---|---|
| list: a[i], a[i] = x, len(a) | O(1) | O(1) | |
| list: append(x), pop() | O(1) | O(1) | amortised: an occasional append resizes the array |
| list: insert(0, x), pop(0), x in a | O(n) | O(1) | |
| dict: d[k], d[k] = v, del d[k], k in d | O(1) | O(1) | average; O(n) if every key collides |
| set: add(x), discard(x), x in s | O(1) | O(1) | average, as for dict |
| deque: append, appendleft, pop, popleft | O(1) | O(1) | |
| deque: d[i] in the middle | O(n) | O(1) | |
| heapq: heappush, heappop | O(log n) | O(1) | |
| sorted list: bisect_left, bisect_right | O(log n) | O(1) | |
| sorted list: insort | O(n) | O(1) |
The list, dict and set rows come from the Python documentation’s own page of operation costs, which also covers
slices, sorting and the set operations; the deque rows come from the collections documentation, and the heap and
bisect rows from the documentation of those modules, as in the previous lesson.
big-O notation: A way to say how the work of an algorithm grows with the size of its input: O(n) grows at most in proportion to n, O(√n) with the square root of n. More in “Solving problems with loops step by step” describes how a cost grows. The model below turns each growth rate of the table into a time, for a machine that does 100 million simple steps a second. It starts at 100,000 items, the largest size this lesson measures:
For n = 100 thousand at 100 million operations a second: O(n log n) takes 17 ms, O(n²) 1.7 minutes and O(2ⁿ) forever. Within a second: O(1), O(log n), O(n), O(n log n).
The numbers as a table
| Growth rate | Operations | Time |
|---|---|---|
| O(1) | 1 | 10 ns |
| O(log n) | 16.6 | 166 ns |
| O(n) | 100 thousand | 1 ms |
| O(n log n) | 1.7 million | 17 ms |
| O(n²) | 10 billion | 1.7 minutes |
| O(n³) | 1,000 trillion | 115.7 days |
| O(2ⁿ) | too many to count | forever |
At 100,000 items an O(1) operation is one step, about 10 ns, O(log n) about 17 steps and O(n) a millisecond, while O(n²) work, such as a scan of the list for every one of its items, takes 1.7 minutes. Big-O says nothing about the constant in front, though, and for small collections the constant is what you measure.
Key idea
The costs follow from the build: a list is an array, cheap at its end and by position; a dict and a set are hash tables, cheap for any key on average; a deque is cheap at both ends.
Checkpoint
Check: What each structure costs
Measured: a set answers in the same time at every size
timeit runs a statement many times and reports the time. Its documentation advises taking the best of several
repeats, because slower runs are usually caused by other programs rather than by Python, and this example follows
that advice:
import timeit
def per_lookup_ns(stmt, names):
"""Nanoseconds per run of stmt: the best of 5 repeats, each about 20 ms long."""
timer = timeit.Timer(stmt, globals=names)
once = max(timer.timeit(number=1), 1e-7)
number = max(1, int(0.02 / once))
return min(timer.repeat(repeat=5, number=number)) / number * 1e9
print(f"{'items':>7} {'list, absent':>12} {'list, first':>11} {'set':>5} {'dict':>5} {'set(list) once':>14}")
for n in [10, 1_000, 100_000]:
names = {"data": list(range(n))}
names["as_set"] = set(names["data"])
names["as_dict"] = dict.fromkeys(names["data"])
cells = [
per_lookup_ns("-1 in data", names), # a value that is not there: every item is checked
per_lookup_ns("0 in data", names), # the first item: found at once
per_lookup_ns("-1 in as_set", names),
per_lookup_ns("-1 in as_dict", names),
per_lookup_ns("-1 in set(data)", names), # building a set for one question
]
print(f"{n:>7,} {cells[0]:>10,.0f}ns {cells[1]:>9,.0f}ns {cells[2]:>3,.0f}ns {cells[3]:>3,.0f}ns {cells[4]:>12,.0f}ns") Output
items list, absent list, first set dict set(list) once
10 99ns 12ns 16ns 16ns 185ns
1,000 10,626ns 12ns 16ns 15ns 25,830ns
100,000 2,657,080ns 12ns 16ns 16ns 2,648,754ns
This output changes from run to run: it measures time, which differs from one run to the next and from one computer to another; the computer of this run is named below the output.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 membership_timing.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Four things stand out in the recorded run, and in yours they will have the same shape even where the numbers differ:
- A set and a dict take the same time at every size. Their columns barely move from 10 items to 100,000.
- A list scan grows with the list. Looking for a value that is not there checks every item, so the time grows in step with the length of the list. At 100,000 items the scan takes more than ten thousand times as long as the set lookup.
- Where the item is matters to a list. The first item is found after one comparison, as fast as a set lookup. For a handful of items, or for lists where the item searched for is usually near the front, a list is fine.
- Building a set for one question saves nothing.
x in set(data)has to visit every item to build the set before it can look, so its column grows with the list just like the scan does. Convert a list to a set when you will ask many questions, and keep the set.
Checkpoint
Check: Measuring membership with timeit
A membership test on a list makes its loop quadratic
The most common slow program in Python has a membership test on a list inside a loop. Each test scans the list, the list grows with every new item, and so the comparisons add up to an amount that grows with the square of the input size, quadratic time: Work that grows with the square of the input size, O(n²): ten times the input takes about a hundred times as long. A membership test on a list inside a loop over the items is the usual cause.. Removing duplicates from a list of ids is the classic case:
import time
ids = [i * 7919 % 2_000 for i in range(2_000)] * 2 # 2,000 different ids, each seen twice
def unique_with_list(values):
seen = []
for v in values:
if v not in seen: # scans the whole list every time
seen.append(v)
return seen
def unique_with_set(values):
seen = set()
order = []
for v in values:
if v not in seen: # one hash lookup
seen.add(v)
order.append(v)
return order
for unique in (unique_with_list, unique_with_set):
start = time.perf_counter()
result = unique(ids)
print(f"{unique.__name__}: {len(result):,} unique ids in {(time.perf_counter() - start) * 1000:.1f} ms") Output
unique_with_list: 2,000 unique ids in 50.9 ms unique_with_set: 2,000 unique ids in 0.2 ms
This output changes from run to run: it measures time, which differs from one run to the next and from one computer to another; the computer of this run is named below the output.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 dedupe_timing.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Both functions return the same ids in the same order: the set only answers “seen before?”, and a list keeps the order. With 2,000 ids seen twice each, the list version already takes many times longer, and because its work grows with the square of the count, ten times as many ids would make it about a hundred times slower, while the set version would take only about ten times as long. The model in the section on costs shows the same step: set its size to 2,000 and then to 20,000, and O(n²) goes from 40 ms to 4 s while O(n) goes from 20 µs to 200 µs.
A list as the memory of a loop
if v not in seen: seen.append(v) with seen = [] scans every id seen so far, on every pass. Keep a set for the
question, seen = set() and seen.add(v), and a list only for the order, as unique_with_set does.
Every time a loop contains if x in some_list or some_list.index(x), ask whether a set or a dict could answer the
question instead.
Checkpoint
Check: The quadratic membership trap
Sets and dicts take four to five times the memory of a list
Speed is not the only cost. A hash table keeps spare slots so that collisions stay rare, which makes sets and dicts larger than lists of the same items:
import sys
from collections import deque
items = list(range(1_000))
containers = {
"list": list(items),
"tuple": tuple(items),
"deque": deque(items),
"set": set(items),
"dict": dict.fromkeys(items),
}
# getsizeof counts each container itself, not the 1,000 int objects it refers to.
for name, container in containers.items():
print(f"{name:5} of 1,000 items: {sys.getsizeof(container):>6,} bytes") Output
list of 1,000 items: 8,056 bytes tuple of 1,000 items: 8,048 bytes deque of 1,000 items: 8,680 bytes set of 1,000 items: 32,984 bytes dict of 1,000 items: 36,952 bytes
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 memory_sizes.py
sys.getsizeof() measures the container alone, not the 1,000 int objects it refers to, which are the same for all
five. On this 64-bit build the list, the tuple and the deque take about 8 KB, mostly one 8-byte reference per item,
while the set takes about four times as much and the dict about four and a half times. The browser runner is a
32-bit build with other sizes, so this example shows a recorded run instead of a Run button. For a few thousand items
the difference rarely matters; for tens of millions, it decides whether the data fits in memory, and the array
module of the previous lesson saves even more for plain numbers.
Checkpoint
Check: Memory of the containers
Match the structure to your own program
The table pairs common situations with the structure to choose and the one to avoid:
| Situation | Choose | Avoid |
|---|---|---|
| Find records by an id or a name | dict |
a list searched with a loop |
| Remove duplicates, count distinct values, test membership | set (or dict.fromkeys() to keep order) |
if x not in some_list in a loop |
| Process jobs in arrival order | collections.deque |
list.pop(0) |
| Count how often each value occurs | collections.Counter |
a dict updated by hand |
| Always take the most urgent or the earliest item | heapq on a list |
sorting the whole list each time |
| Answer range questions on data that rarely changes | a sorted list with bisect |
scanning for every question |
| A fixed record, or a key made of several values | tuple |
a list, which cannot be a key |
| Items read and replaced by position | list |
a dict with numbers 0 to n - 1 as keys |
For a program of your own, answer the two questions above the next table. It marks the structures that fit, rules the others out with the reason, and shows the cost of each in the lesson’s own numbers:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Structure | Find a value by key | Ask "is it there?" | Take the first in | Take the smallest | Sorted order and ranges |
|---|---|---|---|---|---|
| list Built as: an array of references | Poor no keys: finding a record means a scan, O(n) | Poor x in a scans: 2.66 ms at 100,000 items in the run | Poor pop(0) moves every other item, O(n) | Poor min() reads every item, O(n) | Mixed sort() once, then bisect it |
| dict Built as: a hash table of keys and values | Good d[k] is one hash lookup, O(1) on average | Good k in d took about 16 ns at every size in the run | Poor keeps insertion order, but it is not a queue | Poor min() reads every key, O(n) | Poor insertion order only, never sorted order |
| set Built as: a hash table of keys | Poor keys only: there is no value to look up | Good x in s took about 16 ns at every size in the run | Poor no order to take the items in | Poor min() reads every item, O(n) | Poor no order at all |
| collections.deque Built as: a sequence built for both ends | Poor no keys: a scan, O(n) | Poor x in d scans, O(n) | Good popleft() and appendleft() in O(1) | Poor min() reads every item, O(n) | Poor arrival order only |
| heapq on a list Built as: a list kept in heap order | Poor no keys: a scan, O(n) | Poor x in h scans, O(n) | Poor takes the smallest, not the first in | Good heappop() in O(log n), h[0] in O(1) | Poor only the smallest item has a known place |
| sorted list + bisect Built as: a list kept in sorted order | Mixed bisect on sorted keys, O(log n) | Mixed bisect: about 17 comparisons at 100,000 items | Poor pop(0) moves every other item, O(n) | Mixed a[0] costs O(1), but each insort is O(n) | Good two bisects answer a range question, O(log n) |
How the answers decide
- “Which operation does your program repeat most often?” Find a value by its key: rules out list, set, collections.deque and heapq on a list and adds a caveat to sorted list + bisect. A dict finds the value for a key with one hash lookup at any size. A sorted list of keys can bisect for one in O(log n); the others must scan.
- “Which operation does your program repeat most often?” Ask whether it holds an item: rules out list, collections.deque and heapq on a list and adds a caveat to dict and sorted list + bisect. A set answers x in s with one hash lookup, about 16 ns at every size in the run. A dict does too but keeps values you never use, and bisect needs about 17 comparisons at 100,000 items.
- “Which operation does your program repeat most often?” Take the item that arrived first: rules out list, set, dict, heapq on a list and sorted list + bisect. Only a deque is built to take items from the front in constant time: list.pop(0) moves every other item, and the other structures keep no queue.
- “Which operation does your program repeat most often?” Take the smallest or most urgent item: rules out list, set, dict and collections.deque and adds a caveat to sorted list + bisect. A heap gives the smallest next in O(log n) per push and pop, however the items arrive. A sorted list has it at index 0, but every insort costs O(n).
- “Does it also need the items in sorted order, or answers about ranges?” Yes: rules out set, dict, collections.deque and heapq on a list and adds a caveat to list. Ranges and sorted order need sorted data: two bisects answer each range question in O(log n). A plain list has to be sorted first.
In short: a dict for keys, a set for membership, a deque for arrival order, a heap for the smallest next and a sorted list for ranges. When the answers leave nothing that fits, as for jobs taken in arrival order that also need ranges, the program has two needs, and two structures kept in step serve it better than one.
Python Online Compiler Time your own structures: change the sizes in membership_timing.py and run it in the browser.Checkpoint
Check: Matching a structure to a situation
Practise: unique visitors in constant time
The exercise asks for a class that counts visitors with the structure this lesson recommends for “have I seen this?”. Its sample tests count how often ids are compared, so a list that is scanned on every visit fails them:
Exercise · Easy · Python
Count unique visitors in constant time
A website counts its visitors. Complete the class UniqueVisitors in visitors.py:
add(visitor_id)records one visit and returnsTruewhen this visitor has not been seen before,Falsewhen they have.count()returns how many different visitors have been seen.visits()returns how many visits were recorded in all, repeats included.visitor_id in trackerisTruefor a visitor who has been seen.
Each of these must take constant time on average, however many visitors there are. The sample tests check that with visitor ids that count how often they are compared: an add() that compares the new id with every id seen so far fails long before 2,000 visitors, while one that uses a hash lookup compares almost nothing.
Starter code · visitors.py
class UniqueVisitors:
"""Visits to a website, with the number of different visitors, each operation in constant time."""
def __init__(self):
self.seen = [] # choose a better structure for this
self.total = 0
def add(self, visitor_id):
"""Record a visit; True if this visitor is new, False if they were seen before."""
return True
def count(self):
"""How many different visitors have been seen."""
return 0
def visits(self):
"""How many visits were recorded, repeats included."""
return self.total
def __contains__(self, visitor_id):
return False The sample tests · test_visitors.py
from visitors import UniqueVisitors
COMPARISON_BUDGET = 10_000
class CountedId:
"""A visitor id that counts how often it is compared with another id for equality."""
comparisons = 0
def __init__(self, number):
self.number = number
def __hash__(self):
return hash(self.number)
def __eq__(self, other):
CountedId.comparisons += 1
if CountedId.comparisons > COMPARISON_BUDGET:
raise AssertionError(
f"more than {COMPARISON_BUDGET:,} comparisons between ids: add() and `in` must not compare the new id "
"with every id seen so far; use a hash lookup"
)
return isinstance(other, CountedId) and self.number == other.number
def test_add_and_count():
"""counts each visitor once and reports whether a visit is a first visit"""
tracker = UniqueVisitors()
assert tracker.add("asha") is True
assert tracker.add("ravi") is True
assert tracker.add("asha") is False
assert tracker.count() == 2
assert tracker.visits() == 3
def test_contains():
"""answers `in` for seen and unseen visitors"""
tracker = UniqueVisitors()
for visitor in ["u1", "u2", "u1"]:
tracker.add(visitor)
assert "u1" in tracker
assert "u3" not in tracker
def test_empty():
"""starts with no visitors and no visits"""
tracker = UniqueVisitors()
assert tracker.count() == 0
assert tracker.visits() == 0
assert "anyone" not in tracker
def test_mixed_id_types():
"""treats ids of different types as different visitors"""
tracker = UniqueVisitors()
assert tracker.add(7) is True
assert tracker.add("7") is True
assert tracker.add((7, "mobile")) is True
assert tracker.add(7) is False
assert tracker.count() == 3
def test_constant_time():
"""handles 2,000 visitors, each visiting twice, with few id comparisons"""
CountedId.comparisons = 0
tracker = UniqueVisitors()
for round_ in range(2):
for n in range(2_000):
assert tracker.add(CountedId(n)) is (round_ == 0)
for n in range(0, 2_000, 7):
assert CountedId(n) in tracker
assert CountedId(5_000) not in tracker
assert tracker.count() == 2_000
assert tracker.visits() == 4_000 A hint
self.seen starts as a list, and visitor_id in self.seen on a list compares the id with every item until it finds a match. Which structure answers "have I seen this before?" with one hash lookup instead? With that structure, count() is just its length, and add() only needs to look before it adds, and to count every visit.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
The self-check below draws on every section. It asks first about the idea your answers show you know least, and every choice it offers explains itself once you pick it:
Practice that adapts to you
Practise this lesson
19 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · Which Python structures would you use for a stack, a queue and a priority queue, and why?
A stack, last in first out, is a plain list: append() pushes and pop() takes the last item, both O(1) at the end
of the list. A queue, first in first out, is a collections.deque: append() adds at the back and popleft()
takes from the front in O(1), whereas list.pop(0) moves every other item and costs O(n). A priority queue, where
the smallest or most urgent item leaves first, is a list kept as a heap with heapq: heappush() and heappop()
take O(log n). A good answer adds that equal priorities need a tie-breaker such as a running counter in the tuple,
and that queue.Queue and queue.PriorityQueue are the versions to use when several threads share the queue.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A game server keeps 2 million (score, player) pairs in a list and calls sorted() on every request to show the top 10 and the requesting player's rank. Scores change thousands of times a second. Redesign the in-memory structures and give the cost of each operation.
-
Mid to senior
Design an in-process rate limiter: each user may make at most 100 requests in any 60-second window. Which structures hold the state, what does one request cost, and how do you stop memory from growing with users who have stopped calling?
Key takeaways
- List the operations your program repeats, then choose the structure that makes those cheap: dict for keys, set for membership, deque for queues, heap for the smallest next, sorted list with bisect for order and ranges, list for positions.
- A list is an array of references: indexing and
append()are O(1), whileinsert(0, x),pop(0)andinare O(n). Dicts and sets are hash tables with O(1) average lookups. - Measure with
timeitand keep the best of several repeats: a set and a dict answered in the same time at every size, and building a set for one question costs a pass over every item. x in some_listinside a loop makes the whole loop O(n²); a set or a dict turns it back into O(n).- Sets and dicts take four to five times the memory of a list of the same items, which matters for tens of millions.
- When two operations matter equally, two structures kept in step, such as a dict and a heap, beat one that suits neither.
Terms in this lesson
- amortised cost
- The cost of an operation averaged over a long run of them. list.append() is amortised O(1): most appends use spare room at the end of the list, and the rare one that grows the array is paid for by the many before it.
- quadratic time
- Work that grows with the square of the input size, O(n²): ten times the input takes about a hundred times as long. A membership test on a list inside a loop over the items is the usual cause.
References
- Time complexity of operations on built-in types (Python documentation) (Python Software Foundation)
- Design and History FAQ: how are lists implemented in CPython? (Python Software Foundation)
- Design and History FAQ: how are dictionaries implemented in CPython? (Python Software Foundation)
- collections.deque (The Python Standard Library) (Python Software Foundation)
- heapq: heap queue algorithm (The Python Standard Library) (Python Software Foundation)
- bisect: array bisection algorithm (The Python Standard Library) (Python Software Foundation)
- timeit: measure execution time of small code snippets (Python Software Foundation)
- queue: a synchronized queue class (The Python Standard Library) (Python Software Foundation)
- sys.getsizeof() (The Python Standard Library) (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress