Transparency: 1stepGrow may earn a commission or fee from some links on this page, at no extra cost to you. See our affiliate and advertising disclosure.
On this page
The short answer on lists vs tuples in Python: lists are mutable and tuples are not. Use a list for a collection of similar items that will grow or change, and a tuple for a fixed record, a dictionary key or a value that must not change. Every other difference follows from mutability.
"Tuples are immutable" still leaves traps. A tuple that holds a list cannot be a dictionary key, and a list used as a default argument quietly carries data from one function call to the next. This comparison is for Python learners who know both types exist but are unsure which to reach for. It covers hashability, memory, safety and intent, with a one-question decision rule. For everything you can do with tuples themselves, see the Python tuples guide.
Option A
List
Mutable, ordered, growable — the default sequence.
- Best for
- Homogeneous collections that change over time
Option B
Tuple
Immutable, hashable, fixed — a record or a key.
- Best for
- Fixed-size records, dict keys, and signalling immutability
Feature-by-feature comparison
| Feature | List | Tuple |
|---|---|---|
| Mutable | Yes | No |
| Hashable (usable as dict key / set member) | No | Yes, if contents are hashable |
| Memory per element | Higher (over-allocates for growth) | Lower (exact size) |
| Creation speed | Slower | Faster |
| Iteration speed | Comparable | Comparable, marginally faster |
| Can grow or shrink | Yes - append, extend, pop | No - concatenation makes a new tuple |
| Sortable in place | Yes - .sort() | No - sorted() returns a list |
| Typical use | A collection of similar things | One record with fixed fields |
| Signals intent | This will change | This will not change |
| Safe as a default argument | No - classic mutable default bug | Yes |
The verdict
List takes it
Lists are the right default for most code — the majority of sequences you build are homogeneous collections that grow, and mutability is what you want. Tuples earn their place in three specific situations: fixed-size records, keys for dictionaries and sets, and anywhere you want the language to enforce that something does not change. Choose on intent rather than on the small performance differences.
Choose List
You have a collection of similar items that will grow, shrink, be sorted or be modified. This describes most sequences in most programs.
Choose Tuple
You have a fixed-size record with meaningful positions, you need a dictionary key or set member, or you want immutability enforced.
What is the real difference between a list and a tuple?
Mutability is the root difference, and three practical consequences follow from it. The Python design FAQ, why are there separate tuple and list data types, frames tuples like C structs and lists like arrays.
Hashability. A hash must stay constant for the lifetime of the object, or dictionary lookups break. Since a list can change, it cannot be hashed. Since a tuple cannot change, it can — provided everything inside it is hashable too.
valid = {(1, 2): "point"}
invalid = {[1, 2]: "point"} # TypeError (unhashable type: 'list')
# a tuple containing a list is also unhashable
{(1, [2]): "x"} # TypeError (unhashable type: 'list')
Memory. A list over-allocates so that repeated append is amortised O(1). By contrast, a tuple allocates exactly what it needs. You can see this with sys.getsizeof:
import sys
sys.getsizeof([1, 2, 3]) # 88 on 64-bit Python 3.14
sys.getsizeof((1, 2, 3)) # 72 on 64-bit Python 3.14
These figures were measured as of September 2026 on Python 3.14. The exact numbers change between Python versions and platforms, but the tuple is consistently smaller. That is meaningful at scale — a million small tuples versus a million small lists is a real difference — and irrelevant otherwise.
Safety as a default argument. This is the practical one, and the Python programming FAQ covers it too:
# broken - the default list is created once and shared
def add_item(item, basket=[]):
basket.append(item)
return basket
add_item("a") # ['a']
add_item("b") # ['a', 'b'] <- surprise
# correct
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
Tuples, however, do not have this failure mode, because you cannot mutate them.
How to decide between lists vs tuples in Python
To decide, ask one question: is this a collection of similar things, or one record with fixed fields?
A collection — user IDs, temperatures, rows to process — is a list. It is homogeneous, its length depends on the data, and you will probably append to it or sort it.
On the other hand, a record — a coordinate, an RGB colour, a (name, age, city) triple — is a tuple. Its length is fixed by meaning, its positions have distinct roles, and changing it does not conceptually make sense.
# collection of similar things
temperatures = [18, 25, 31, 12, 28]
temperatures.append(30)
# one record, fixed fields
location = (19.0760, 72.8777)
# a list of records
cities = [
("Mumbai", 19.0760, 72.8777),
("Delhi", 28.6139, 77.2090),
]
That last pattern — a list of tuples — is extremely common, and it shows the rule working in both directions at once.
When to use a tuple vs a list: quick reference
| Situation | Choose | Why |
|---|---|---|
| Rows you will filter, sort or append to | list | needs mutation |
| A coordinate, colour or date parts | tuple | fixed fields |
| A dictionary key or set member | tuple | must be hashable |
| Several return values from a function | tuple | Python packs them automatically |
| Constants such as allowed extensions | tuple | cannot be changed by accident |
| A default argument value | tuple or None |
avoids the shared-default bug |
Where do tuples beat lists in real code?
Tuples win in four situations: compound dictionary keys, set members, multiple return values and constants. Each is shown below with real output.
Compound dictionary keys:
from collections import defaultdict
transactions = [("North", "Q1", 1200.0), ("North", "Q1", 300.5), ("South", "Q1", 800.0)]
sales = defaultdict(float)
for region, quarter, amount in transactions:
sales[(region, quarter)] += amount
dict(sales) # {('North', 'Q1'): 1500.5, ('South', 'Q1'): 800.0}
Set membership for pairs:
edges = [(1, 2), (2, 1), (2, 3)]
seen_edges = set()
for a, b in edges:
key = (min(a, b), max(a, b))
if key in seen_edges:
continue
seen_edges.add(key)
sorted(seen_edges) # [(1, 2), (2, 3)] - the duplicate edge is skipped
Returning several values:
def describe(nums):
return min(nums), max(nums), sum(nums) / len(nums)
lo, hi, avg = describe([3, 1, 4, 1, 5]) # 1, 5, 2.8
Fixed configuration you do not want mutated:
ALLOWED_EXTENSIONS = (".csv", ".json", ".parquet")
"report.json".endswith(ALLOWED_EXTENSIONS) # True - endswith accepts a tuple
When is a list the right choice?
Lists are right for anything you build up, filter, sort or modify:
scores = [("Ananya", 91), ("Rohit", 78), ("Meera", 85)]
results = []
for name, score in scores:
if score >= 80:
results.append((name, score))
results.sort(key=lambda r: r[1], reverse=True)
results # [('Ananya', 91), ('Meera', 85)]
Note that sorted() on a tuple returns a list, because it has to — there is no in-place sort for an immutable type.
Common mistakes when choosing between lists and tuples
- Calling list methods on a tuple.
(1, 2).append(3)raisesAttributeError: 'tuple' object has no attribute 'append'. - Using a list as a dict key. Convert it with
tuple()first. - Assuming a tuple is frozen all the way down. A tuple holding a list can still change.
- Choosing tuples "for speed". The gain is marginal; choose on intent.
- Using a list as a default argument. Use
None, or a tuple if the default is read-only.
What should you use when tuple positions get hard to read?
Use a NamedTuple. When p[2] no longer tells a reader what the value is, upgrade rather than tolerating it:
# unclear
p = ("Ananya", 28, "Pune")
print(p[2])
# clear
from typing import NamedTuple
class Person(NamedTuple):
name: str
age: int
city: str
p = Person("Ananya", 28, "Pune")
print(p.city) # Pune
As a result, you keep tuple behaviour — immutability, hashability, unpacking — and gain names. Our tuples guide covers named tuples in more depth.
Should you default to a list or a tuple?
Use a list by default. Then reach for a tuple when you need a key, a record, or enforced immutability.
In short, do not agonise over the performance difference in the lists vs tuples in Python debate. It is real, but it is almost never the thing that matters in your program.
The Sunday Growth Brief
One email a week: the best new comparisons, a fresh roadmap and the tech news worth your attention.
No spam. Unsubscribe in one click.
Related reading
Mastering Python tuples goes deeper on tuple features. Python basics covers the other core types, and more head-to-head breakdowns sit in the comparisons hub.
Frequently asked questions
Are tuples faster than lists?
Marginally, for creation, and they use less memory because they do not over-allocate. Iteration speed is essentially the same. The differences are small enough that they should almost never drive your choice, so pick on mutability and intent instead, and measure with timeit if a hot loop genuinely depends on it.
Why can tuples be dictionary keys but not lists?
Dictionary keys must be hashable, and hashability requires the value not to change. Otherwise the hash computed at insertion would no longer match after a mutation, and lookups would silently fail. Immutability is what makes tuples safe here, provided every element inside the tuple is hashable too, so a tuple holding a list still cannot be a key.
Can I convert between lists and tuples?
Yes, with list(my_tuple) and tuple(my_list). Both create a new object and copy the references to the same elements, so the conversion is O(n) and the elements themselves are not duplicated. A common pattern is to build a list while collecting data, then call tuple() once at the end to freeze the result.
Why is a mutable default argument a bug?
Default arguments are evaluated once, when the function is defined, not on each call. A default list is therefore shared across every call, and mutations accumulate from one call to the next. Use None as the default and create the list inside the function. A tuple default avoids the problem, because it cannot be mutated.
Written by
Ayushi Kulshreshta
AI Engineer
AI engineer building retrieval-augmented systems, previously a software developer at VDB Inc. and an R&D associate at Nokia.

