Built-in collections: list, tuple, dict and set — what to use when
The question of whether to choose a list, a tuple, a dictionary or a set sounds like a matter of taste, but it is settled by three checks: do you need order, do you need repeats, and how will you search. A run answers with numbers: the check x in on a list of a thousand elements is 216 times slower than on a set, and on ten elements only 3.4 times (CPython 3.13.7, one machine); a dictionary keeps insertion order, while a set of strings prints differently under different PYTHONHASHSEED values; and a set of a thousand numbers is 4.1 times bigger than a list.
Full technical treatment
TL;DR
Python has four main built-in collections, and you choose between them by three questions: do you need order, do you need repeats, and how will you search. A list is ordered and mutable, with repeats. A tuple is the same, but immutable. A dictionary holds keys and values, and the keys are unique. A set holds unique elements without order.
Hence the main consequence: when there are many "is this element there"
checks, they are worth doing on a set or a dictionary, not on a list. A list
checks the elements one by one; a set finds the element by its hash — a number
the object computes from its value (hash(x)). On average that is O(1): the
time hardly depends on the size; in the worst case, when hashes coincide, it is
O(n) (in detail — in the lesson "Complexity of operations"). In our run
(CPython 3.13.7, one machine) x in on a list of a thousand elements was 216
times slower than on a set, on a hundred thousand — thirteen thousand times;
on your machine the ratio has to be measured. The price is memory and
hashability: a set of a thousand numbers is 4.1 times bigger than a list, and
set elements and dictionary keys must be hashable — a list cannot be one.
Dictionary values need not be: {"items": []} works.
Beyond that comes what separates understanding from having read about it.
On ten elements the difference is only 3.4 times, and the list finds its first
element as fast as the set does; and building a set from a list for a single
check did not pay off — it took longer than one scan. Since Python 3.7 a dictionary keeps insertion
order — a language guarantee — while a set promises no order: a set of five
strings printed in three different orders under three different
PYTHONHASHSEED values. And a queue built on a list is a mistake:
with a hundred thousand elements list.pop(0) costs 299 times more than
deque.popleft().
- how to create a list and a dictionary and loop over them with
for; - what a mutable and an immutable object are;
- that the expression
x in ychecks membership; - what a class and an instance are — the lesson "Classes and OOP": it explains why instances of ordinary classes are hashable;
- the notation O(1) and O(n) — at least as "does not grow" and "grows with the size"; in detail — in the lesson "Complexity of operations".
- hashability,
frozenset,dict.fromkeys; - the order of a dictionary and of a set,
PYTHONHASHSEED; - the cost of
x in,list.pop(0)andcollections.deque.
Base: four collections and three questions
| collection | order | repeats | mutable | how it is searched |
|---|---|---|---|---|
list | yes | yes | yes | by index; by value — by scanning |
tuple | yes | yes | no | by index; by value — by scanning |
dict | insertion order | keys are unique | yes | by key, via the hash |
set | no | no | yes | by value, via the hash |
A junior-level answer to "what to use when": a list for an ordered collection that changes; a tuple for an immutable record of a few fields; a dictionary when a value is looked up by key; a set when you need unique elements and a fast "is it there" check. That is correct, and it is usually followed by three follow-up questions: how much faster the check on a set is, why a list cannot be put into a set, and what about order. That is what the mechanisms below are about.
How a list differs from a tuple in memory and speed is a separate lesson, "List versus tuple". Here the choice is among all four.
Mechanism 1: repeats and uniqueness
Output of bench/collections/basics.py, section 1 (labels translated):
1) duplicates
list(data) -> [3, 1, 3, 2, 1]
set(data) -> {1, 2, 3}
dict.fromkeys(data) -> {3: None, 1: None, 2: None}
{'a': 1, 'a': 2} -> {'a': 2}
len(set(data)) == len(data) -> False
A list keeps everything put into it. A set keeps only distinct elements, and the order of the source list is lost. A dictionary keeps unique keys, and when a key is repeated the last value wins.
This output gives two working techniques:
- "Are there duplicates" —
len(set(data)) != len(data). - "Remove duplicates, keeping the order" —
list(dict.fromkeys(data)). Asetcannot do this: a set has no order (Mechanism 3), while a dictionary does.
Mechanism 2: hashability — who can be a key
Set elements and dictionary keys must be hashable; dictionary values need not be. A set and a dictionary find an element or a key by its hash, so it must have a hash, and the hash must not change; a dictionary value sits next to its key, nothing is looked up by it, and there is no reason to hash it:
Values that are not hashable, that is, values containing lists, dictionaries
or other mutable types (that are compared by value rather than by object
identity) may not be used as keys.
A short complete example shows it. The full file
bench/collections/hashable.py:
import sys
print("PY", sys.version.split()[0])
d = {"items": []}
d["items"].append(1)
print('{"items": []} after append:', d)
print('{(1, 2): "ok"}:', {(1, 2): "ok"})
for text, make in [('{[]: "x"}', lambda: {[]: "x"}), ("{[1, 2]}", lambda: {[1, 2]})]:
try:
make()
except TypeError as e:
print(text, "-> TypeError:", e)Run it with python3.13 bench/collections/hashable.py; the output on CPython
3.13.7 (the same on 3.11 and 3.12), exactly as printed:
PY 3.13.7
{"items": []} after append: {'items': [1]}
{(1, 2): "ok"}: {(1, 2): 'ok'}
{[]: "x"} -> TypeError: unhashable type: 'list'
{[1, 2]} -> TypeError: unhashable type: 'list'
The dictionary with a list as its value was created and even changed in
place; the tuple became a key. A list fits neither as a key nor as a set
element. More on what is hashable — the output of
bench/collections/basics.py, section 2 (labels translated):
2) hashability: what can go into a set and into dict keys
{[1, 2]} -> TypeError: unhashable type: 'list'
{[1, 2]: 'x'} -> TypeError: unhashable type: 'list'
{(1, 2)} -> no exception
{(1, [2])} -> TypeError: unhashable type: 'list'
{frozenset({1, 2})} -> no exception
{{1, 2}} -> TypeError: unhashable type: 'set'
The line worth remembering is {(1, [2])}. A tuple is hashable only if
everything in it is hashable: the hash of a tuple is computed from the hashes
of its elements, and a list has none. That is why a key "of several fields" is
a tuple of immutable values, and a set of sets is built from frozenset.
Why mutable objects that are compared by value — lists, dictionaries,
sets — have no hash: a dictionary places a key by its hash and later looks it
up by the same hash. If such a key changed after it was inserted, its hash
would change, and the dictionary would look in the wrong place. A value has no
such problem: it is not looked up by hash, so d["items"].append(1) above
breaks nothing. Instances of ordinary classes are mutable but hashable: they
are compared by identity, not by value — see the lesson "Classes and OOP". How
this works inside is covered in the article on dictionaries.
Mechanism 3: order — a dictionary has it, a set does not
Dictionaries preserve insertion order. Note that updating a key does not
affect the order. Keys added after deletion are inserted at the end.
A set has no such guarantee. The order in which it prints depends on the hashes of its elements, and the hash of a string differs from run to run:
By default, the __hash__() values of str and bytes objects are "salted" with
an unpredictable random value. Although they remain constant within an
individual Python process, they are not predictable between repeated
invocations of Python.
That random "seed" can be fixed with the PYTHONHASHSEED environment
variable. The script ran itself three times with different values — the
output of bench/collections/basics.py, section 3. The script uses Russian
fruit names as its strings; below they and the labels are translated, so this
exact text will not appear in the run, and the orders shown are those of the
original strings:
3) order
dict in insertion order: ['melon', 'apple', 'pear']
removed and inserted again — the key is at the end: ['melon', 'pear', 'apple']
a set of strings under different PYTHONHASHSEED values:
PYTHONHASHSEED=1: ['melon', 'pear', 'plum', 'apple', 'cherry']
PYTHONHASHSEED=2: ['cherry', 'apple', 'melon', 'plum', 'pear']
PYTHONHASHSEED=3: ['melon', 'plum', 'apple', 'pear', 'cherry']
distinct orders out of three runs: 3
a set of integers {5, 1, 3}: {1, 3, 5} — the order comes from the hash, not from insertion
hash(1), hash(3), hash(5): 1 3 5 — the hash of a small integer equals the integer itself
a set of integers {8, 1}: {8, 1} — and not always in ascending order
The last lines are a trap in reverse. A set of small integers often prints
"in ascending order", because the hash of a small integer equals the integer
itself. But that is a coincidence of the implementation, not sorting: {8, 1}
printed as {8, 1}, and code that relies on the order will break on
strings. If you need order, use a list, a
dictionary or sorted(...).
Mechanism 4: the cost of a lookup — where the difference is huge and where there is none
The documentation names "fast membership testing" as the first use of sets. How fast is a matter of measurement. The element sought is the last one, which is the worst case for a list:
| size | list | set | dict (keys) | list / set |
|---|---|---|---|---|
| 10 | 119.5 ns | 35.2 ns | 35.4 ns | 3.4 |
| 1,000 | 9,690.0 ns | 44.9 ns | 48.4 ns | 216.0 |
| 100,000 | 1,052,432.4 ns | 79.4 ns | 56.7 ns | 13,250.6 |
A list checks the elements one by one, so its time grows with the size. A set and a dictionary compute the hash and go to the right place in the table; when the hashes are well spread, as with these integers, only a few slots need checking — so their time grows by a few times, not by thousands of times like the list's. A tuple behaves like a list: it is a sequence too.
But O(1) for a set and a dictionary is an average estimate, not a promise
of one check. If many keys share one hash, a lookup compares them one by one,
and the worst case is O(n): in the lesson "Complexity of operations" a lookup
in a set of a hundred objects with the same hash called __eq__ 105 times, not
once. And a single check is not free either: it costs computing the hash and a
call to __eq__, and for a long string both go over its characters.
The numbers also include the for loop the check ran in: an empty loop costs
18.7 ns per step — the "empty loop" row in the run record. At large sizes that
is negligible, but at ten elements it is half of the set's time, so 3.4 times
is a lower bound: the checks themselves differ more.
But the rule "always a set" does not work for small collections. On ten
elements the difference is only 3.4 times, and if the element sought is the
first one, the list finds it in 33.7 ns — as fast as the set. And building a
set for a single check does not pay: building hashes every element and places
it in the table. The measurement above does not see this — there the
collections are ready before the clock starts — so building was measured
separately. The output of bench/collections/build.py (CPython 3.13.7; labels
translated):
building a set for the checks: ns per scenario, medians of 9 rounds
size checks by list set(lst) + checks
10 1 98.8 211.1
10 100 8499.6 1962.7
1000 1 4638.7 7926.6
1000 100 596511.4 10674.0
For a single check, building the set lost to the scan both at ten and at a thousand elements; for a hundred checks it won. A set pays off when there are many checks; if it already exists, it is faster even at ten elements. Where the break-even point lies on your data is a matter of measurement, not of a rule.
Mechanism 5: a queue is not a list
Output of bench/collections/cost.py, section 2 (labels translated):
2) removal from the front: 50,000 removals from a collection of 100,000, ns per removal
(building the collection is not part of the measurement)
form min median max
list.pop(0) 11920.9 12662.3 13358.6
deque.popleft() 41.6 42.4 47.3
list.pop() from the end 42.5 43.7 46.3
list.pop(0) / deque.popleft() by medians: 299
A list stores its elements contiguously, and removing the first one shifts all
the others by one position — the longer the list, the more it costs. Removing
from the end shifts nothing, so list.pop() is cheap. For a queue — "append
to the end, take from the front" — the standard library has
collections.deque, which its docstring describes as "a list-like sequence
optimized for data accesses near its endpoints". In the measurement
popleft() was 299 times cheaper than list.pop(0).
Deeper: what memory costs
Output of bench/collections/basics.py, section 4 (labels translated):
4) memory: the container itself for 1000 integers, bytes (sys.getsizeof)
list 8056
tuple 8040
set 32984
dict 36952
set / list: 4.1
sys.getsizeof counts the container itself, without its elements: the
numbers are shared and do not count. A list and a tuple store one pointer per
element — 8 bytes on a 64-bit build — plus a header. A set and a dictionary
hold a hash table with a reserve of empty slots — without that reserve, lookup
by hash would stop being fast — plus bookkeeping data. Hence 4.1 times more
memory for the set: that is the price of a fast in. But 4.1 is the ratio for
a thousand integers on this version, not a constant: it depends on the number
and kind of elements and on the implementation.
In CPython a set and a dictionary are built differently. In a set each table
slot is a pointer to the element plus its cached hash (setentry,
Include/cpython/setobject.h:20–23 in CPython 3.13.7). A dictionary is an
index table (dk_indices) plus a separate array of entries (dk_entries). A
general entry stores the hash, the key and the value (PyDictKeyEntry,
Include/internal/pycore_dict.h:76–81), while an entry of a dictionary whose
keys are all strings stores only the key and the value (PyDictUnicodeEntry,
lines 83–86): a string keeps its hash in itself (the hash field,
Include/cpython/unicodeobject.h:101). It shows in memory — the output of
bench/collections/basics.py, section 6, identical on 3.11–3.14 (labels
translated):
6) memory and the kind of keys: 1000 keys, bytes (sys.getsizeof)
dict, int keys 36952
dict, str keys 26032
set, int 32984
set, str 32984
A dictionary with the same thousand keys, but strings, is 10,920 bytes smaller; a set's size does not depend on the kind of elements. The details are in the articles on how lists and dictionaries are built.
How to answer in an interview
Short answer: a list is an ordered mutable collection with repeats, a tuple is immutable, a dictionary maps key → value with unique keys, a set holds unique elements without order. Membership checks and lookups by value are done on a set or a dictionary: there it is a hash lookup — O(1) on average and O(n) in the worst case, when hashes coincide — in a list it is a scan. Only a hashable object can be a set element or a dictionary key; dictionary values can be anything. A dictionary keeps insertion order, a set does not.
That is enough for a correct answer. What follows is what you add if the interviewer digs.
If the interviewer digs deeper
On "how much faster is a set". "It depends on the size and on the data.
In my run on CPython 3.13.7 x in on a list of a thousand elements was 216
times slower, on ten — 3.4 times, and the list finds its first element just as
fast. Building a set for a single check is not worth it: in the measurement it
took longer than one scan and paid off at a hundred checks. A set's O(1) is
the average: with coinciding hashes a lookup degenerates into a scan. The
exact ratios on another machine have to be measured."
On "why can't a list go into a set". "A set searches by hash, and the hash
of a mutable object could change after insertion. That is why a list has no
hash. A tuple is hashable only if everything in it is: {(1, [2])} fails.
But a list can be a dictionary value — values are not looked up: {"items": []}
works."
On "is a dictionary ordered". "Since 3.7 insertion order is a language
guarantee. A key that is removed and inserted again goes to the end. A set has
no order: a set of strings printed differently under different
PYTHONHASHSEED values."
On "how to make a queue". "collections.deque. list.pop(0) shifts all
the elements — in my run it cost 299 times more than popleft() on a hundred
thousand."
Next they ask
How do you count how many times each element occurs?
With a dictionary: counts[x] = counts.get(x, 0) + 1. Or with the ready-made
collections.Counter(data) — a dictionary where a missing key gives zero,
plus methods such as most_common(). For grouping, a similar building block
is collections.defaultdict(list).
What is a frozenset and what is it for?
An immutable set. Since it does not change, it has a hash — and it can be put
into another set or used as a dictionary key. An ordinary set will not do for
this: {{1, 2}} fails with unhashable type: 'set'.
How does a dictionary differ from a list of pairs?
In lookup. In a list of pairs [(key, value), …] finding a value by key is a
scan, like x in list. A dictionary finds a key by its hash — on average in
a time that hardly depends on the size — and guarantees that keys are unique. A list of
pairs is appropriate when keys repeat or are not hashable.
Common misconceptions
Membership checks should always be done on a set
If the set already exists and there are many checks — yes. But at ten elements the difference is only 3.4 times, and the list finds its first element in 33.7 ns, like the set. Building a set for a single check means hashing every element: in the measurement on CPython 3.13.7 that took longer than one scan both at ten and at a thousand elements.
Everything in a dictionary must be hashable
Only the keys — and set elements. A value can be a list or another dictionary: {"items": []} is created and changed in place, while {[]: "x"} fails with unhashable type: 'list'.
A set lookup is always one check
O(1) is an average estimate for well-spread hashes. If hashes coincide, a set compares elements one by one: in the lesson "Complexity of operations" a lookup among a hundred objects with one hash called __eq__ 105 times. The worst case is O(n).
A set sorts its elements
A set of small integers often, but not always, prints in ascending order — the hash of such a number equals the number itself; {8, 1} printed as {8, 1}. A set of strings printed in three different orders under different PYTHONHASHSEED values.
A dictionary does not keep order
Since Python 3.7 it keeps insertion order — a language guarantee, not an implementation detail. A key that is removed and inserted again goes to the end.
A tuple can always be a dictionary key
Only if everything in it is hashable. {(1, [2])} fails with unhashable type: 'list'.
A queue is conveniently built on a list with pop(0)
Removing the first element shifts all the others: on a hundred thousand, list.pop(0) cost 299 times more than deque.popleft().
Version history
| Version | Change | What it means for code |
|---|---|---|
| 3.7 | Dictionary insertion order becomes a language guarantee. In CPython 3.6 it was an implementation detail that could not be relied on, and before that there was no order at all. | |
| 3.11 | The reference point of the lesson. The observations of the script on 3.11, 3.12 and 3.13 match line by line, including the orders of the set under the same PYTHONHASHSEED. Entries of a dictionary with string keys without a hash field (PyDictUnicodeEntry) exist in all four versions checked. | |
| 3.14 | The error text for an unhashable object names the role: cannot use 'list' as a set element (unhashable type: 'list') and cannot use 'list' as a dict key (…); for {(1, [2])} — cannot use 'tuple' as a set element (unhashable type: 'list'). A tuple of 1000 elements became 8 bytes bigger — 8048 instead of 8040. The behaviour of the collections did not change. | |
| 3.15 | A new built-in immutable type, frozendict (PEP 814). The lesson did not run it: Python 3.15 was released on 9 October 2026, and our runs are 3.11–3.14. In the words of "What’s New In Python 3.15" (checked on 14.10.2026): "A frozendict is hashable as long as all of its keys and values are hashable" — unlike an ordinary dictionary, where only the keys have to be hashable. |
Practice
Two problems. Answer first, then check against the real output: in both, the correct answer is taken from a run of the script rather than assigned.
Practice · predict the output
words = ["b", "a", "b", "c", "a"] print(list(dict.fromkeys(words))) print(sorted(set(words))) print(len(set(words)), len(words))
Practice · estimate
Check yourself
You need to store unique user ids and often check whether an id is in the collection. What fits best?
How it was measured
The numbers of this lesson come from these scripts. Each one opens right from here — together with the record of its run: what it ran on, what came out and with what spread.
Observations without time — CPython 3.11.15, 3.12.3, 3.13.7 and 3.14.7; time
measurements — 3.13.7 and 3.14.7 on one machine with two cores. The scripts
bench/collections/hashable.py, bench/collections/build.py and section 6 of
bench/collections/basics.py were added and run on 14.10.2026 on
linux/x86_64. Contracts and quotes were checked on 14.10.2026. The lesson did
not run Python 3.15 (released on 9 October 2026) — there is only a line about
it in the version history.
This is neither a retelling nor a separate text: everything below is taken from the article itself — its own summary, the section headings, the “actually” column and the version table. Which is why these theses cannot drift from the article.
The gist
- Python has four main built-in collections, and you choose between them by three questions: do you need order, do you need repeats, and how will you search. A list is ordered and mutable, with repeats. A tuple is the same, but immutable. A dictionary holds keys and values, and the keys are unique. A set holds unique elements without order.
- Hence the main consequence: when there are many "is this element there" checks, they are worth doing on a set or a dictionary, not on a list. A list checks the elements one by one; a set finds the element by its hash — a number the object computes from its value (
hash(x)). On average that is O(1): the time hardly depends on the size; in the worst case, when hashes coincide, it is O(n) (in detail — in the lesson "Complexity of operations"). In our run (CPython 3.13.7, one machine)x inon a list of a thousand elements was 216 times slower than on a set, on a hundred thousand — thirteen thousand times; on your machine the ratio has to be measured. The price is memory and hashability: a set of a thousand numbers is 4.1 times bigger than a list, and set elements and dictionary keys must be hashable — a list cannot be one. Dictionary values need not be:{"items": []}works. - Beyond that comes what separates understanding from having read about it. On ten elements the difference is only 3.4 times, and the list finds its first element as fast as the set does; and building a set from a list for a single check did not pay off — it took longer than one scan. Since Python 3.7 a dictionary keeps insertion order — a language guarantee — while a set promises no order: a set of five strings printed in three different orders under three different
PYTHONHASHSEEDvalues. And a queue built on a list is a mistake: with a hundred thousand elementslist.pop(0)costs 299 times more thandeque.popleft().
In fact
- If the set already exists and there are many checks — yes. But at ten elements the difference is only 3.4 times, and the list finds its first element in 33.7 ns, like the set. Building a set for a single check means hashing every element: in the measurement on CPython 3.13.7 that took longer than one scan both at ten and at a thousand elements.
- Only the keys — and set elements. A value can be a list or another dictionary:
{"items": []}is created and changed in place, while{[]: "x"}fails withunhashable type: 'list'. - O(1) is an average estimate for well-spread hashes. If hashes coincide, a set compares elements one by one: in the lesson "Complexity of operations" a lookup among a hundred objects with one hash called
__eq__105 times. The worst case is O(n). - A set of small integers often, but not always, prints in ascending order — the hash of such a number equals the number itself;
{8, 1}printed as{8, 1}. A set of strings printed in three different orders under differentPYTHONHASHSEEDvalues. - Since Python 3.7 it keeps insertion order — a language guarantee, not an implementation detail. A key that is removed and inserted again goes to the end.
- Only if everything in it is hashable.
{(1, [2])}fails withunhashable type: 'list'. - Removing the first element shifts all the others: on a hundred thousand,
list.pop(0)cost 299 times more thandeque.popleft().
By version
- 3.7
- Dictionary insertion order becomes a language guarantee. In CPython 3.6 it was an implementation detail that could not be relied on, and before that there was no order at all.<
- 3.11
- The reference point of the lesson. The observations of the script on 3.11, 3.12 and 3.13 match line by line, including the orders of the set under the same
PYTHONHASHSEED. Entries of a dictionary with string keys without a hash field (PyDictUnicodeEntry) exist in all four versions checked.< - 3.14
- The error text for an unhashable object names the role:
cannot use 'list' as a set element (unhashable type: 'list')andcannot use 'list' as a dict key (…); for{(1, [2])}—cannot use 'tuple' as a set element (unhashable type: 'list'). A tuple of 1000 elements became 8 bytes bigger — 8048 instead of 8040. The behaviour of the collections did not change.< - 3.15
- A new built-in immutable type,
frozendict(PEP 814). The lesson did not run it: Python 3.15 was released on 9 October 2026, and our runs are 3.11–3.14. In the words of "What’s New In Python 3.15" (checked on 14.10.2026): "A frozendict is hashable as long as all of its keys and values are hashable" — unlike an ordinary dictionary, where only the keys have to be hashable.<
What is covered
- Base: four collections and three questions
- Mechanism 1: repeats and uniqueness
- Mechanism 2: hashability — who can be a key
- Mechanism 3: order — a dictionary has it, a set does not
- Mechanism 4: the cost of a lookup — where the difference is huge and where there is none
- Mechanism 5: a queue is not a list
- Deeper: what memory costs
- How to answer in an interview
- Next they ask
- Common misconceptions
- Version history
- Practice
- Check yourself
- How it was measured
Sources & further reading
6 SOURCES
- The data model — setsOfficial documentation. What a set is and what it is for: «These represent unordered, finite sets of unique, immutable objects». And the uses named outright: «Common uses for sets are fast membership testing, removing duplicates from a sequence, and computing mathematical operations such as intersection, union, difference, and symmetric difference». The text was checked against the built-in pydoc help (the pydoc_data.topics module) of CPython 3.13.7 and 3.14.7.https://docs.python.org/3.13/reference/datamodel.html#the-standard-type-hierarchy
- Built-in types — dictionariesOfficial documentation. The order guarantee: «Dictionaries preserve insertion order. Note that updating a key does not affect the order. Keys added after deletion are inserted at the end». And since which version: «Changed in version 3.7: Dictionary order is guaranteed to be insertion order. This behavior was an implementation detail of CPython from 3.6». And which keys are allowed: «Values that are not hashable, that is, values containing lists, dictionaries or other mutable types (that are compared by value rather than by object identity) may not be used as keys». Checked against pydoc_data.topics of 3.13.7 and 3.14.7.https://docs.python.org/3.13/library/stdtypes.html#mapping-types-dict
- The data model — __hash__Official documentation. Why the order of a set of strings changes from run to run: «By default, the __hash__() values of str and bytes objects are “salted” with an unpredictable random value. Although they remain constant within an individual Python process, they are not predictable between repeated invocations of Python». And what that means for sets: «Changing hash values affects the iteration order of sets. Python has never made guarantees about this ordering». Checked against pydoc_data.topics of 3.13.7 and 3.14.7.https://docs.python.org/3.13/reference/datamodel.html#object.__hash__
- collections.dequeOfficial documentation. The type's docstring in the interpreter: «A list-like sequence optimized for data accesses near its endpoints». Checked by calling collections.deque.__doc__ on 3.13.7 and 3.14.7.https://docs.python.org/3.13/library/collections.html#collections.deque
- Include/internal/pycore_dict.h and Include/cpython/setobject.hCPython source code. How table entries are laid out in CPython 3.13.7 (checked against the headers shipped with the interpreter, 14.10.2026). A general dict entry — pycore_dict.h:76–81, PyDictKeyEntry: «/* Cached hash code of me_key. */» and the fields me_hash, me_key, me_value. An entry of a dict with string keys — lines 83–86, PyDictUnicodeEntry: only me_key and me_value, «/* The key must be Unicode and have hash. */». The dict's hash table itself is the index array dk_indices, lines 167–180: «Actual hash table of dk_size entries. It holds indices in dk_entries». A set entry — setobject.h:20–23, setentry: key and hash, «/* Cached hash code of the key */». On 3.14.7 the same structures are at pycore_dict.h:80–90; PyDictUnicodeEntry is also in the 3.11 and 3.12 headers.https://github.com/python/cpython/blob/v3.13.7/Include/internal/pycore_dict.h
- What’s New In Python 3.15 — frozendictOfficial documentation. PEP 814: «A new immutable type, frozendict, is added to the builtins module. … A frozendict is hashable as long as all of its keys and values are hashable». Python 3.15 was released on 9 October 2026; the lesson did not run it — the quote was checked on 14.10.2026.https://docs.python.org/3.15/whatsnew/3.15.html