When to Use Python Tuples Over Lists: The Immutability Advantage
Problem
When I first learned Python, I kept using lists everywhere. They’re flexible, you can modify them, and they work fine for most cases. Then I saw experienced developers using tuples, and I wondered: why bother with tuples when lists can do everything tuples can do, plus more?
I asked myself: “What’s the point of a data structure that can’t be modified?”
I think the confusion comes from thinking about tuples as “immutable lists” rather than understanding what they’re actually designed for.
What I discovered
I tried to understand the practical differences by using both in my code. Here’s what I found:
# List: mutable, can changecoordinates = [10, 20]coordinates[0] = 99 # Works, but is this what I want?
# Tuple: immutable, prevents accidental changescoordinates = (10, 20)coordinates[0] = 99 # TypeError: 'tuple' object does not support item assignmentAt first, I thought the tuple version was just being restrictive. But then I encountered a real bug in production.
The bug that changed my thinking
I was working on a function with a default argument:
def add_item(item, items=[]): items.append(item) return itemsWhen I called this function multiple times:
print(add_item(1)) # [1]print(add_item(2)) # [1, 2] - Wait, where did the 1 come from?The list persisted across function calls. This is Python’s mutable default argument gotcha—the list is created once when the function is defined, not each time it’s called.
I tried fixing it with a tuple:
def add_item(item, items=None): if items is None: items = [] items.append(item) return itemsNow it works correctly:
print(add_item(1)) # [1]print(add_item(2)) # [2]But I realized the deeper issue: I was using a mutable structure (list) for something that should have been immutable from the start.
Why tuple immutability matters
After dealing with this bug and working on larger codebases, I found several practical reasons to use tuples:
1. Enforcing “this should never change”
In large codebases with multiple developers, data gets passed around through different functions. When you use a list, anyone can modify it:
# In some function far awaydef process_location(coords): coords[0] = 0 # Someone accidentally modified this
# Your original datacoordinates = [10, 20]process_location(coordinates)# Now coordinates is [0, 20] - silent bug!With a tuple, this can’t happen:
coordinates = (10, 20)process_location(coordinates)# TypeError prevents the bugI think the key insight is that tuples enforce constraints at the language level. In a 50,000-line codebase, you can’t track who might modify data. Immutability moves that burden from “developer discipline” to “language guarantees.”
2. Semantic meaning: position matters
I realized tuples represent records where each position has specific meaning, while lists represent collections of similar items:
# Tuple: position has meaning# Format: (red, green, blue)color = (255, 128, 0)
# List: collection of similar itemsshades = [128, 150, 200, 255]When I see color[0], I know it’s the red value. When I see shades[0], it’s just the first item in a collection.
This distinction becomes clearer with type hints:
from typing import Tuple
# Each position has a specific typeuser_info: Tuple[str, int, bool] = ("Alice", 30, True)# (name, age, is_active)
name = user_info[0] # strage = user_info[1] # intactive = user_info[2] # boolLists are homogeneous—all elements have the same type:
ages: list[int] = [30, 25, 40, 35]3. Dictionary keys and set elements
I ran into this when trying to cache location data:
# This doesn't workcache = { [40.7128, -74.0060]: "New York" # TypeError: unhashable type: 'list'}Lists can’t be dictionary keys because they’re mutable—if the list changes, the hash would change, breaking the dictionary lookup.
But tuples work:
cache = { (40.7128, -74.0060): "New York", (51.5074, -0.1278): "London"}
print(cache[(40.7128, -74.0060)]) # "New York"The same applies to set elements:
# Validlocations = {(40.7128, -74.0060), (51.5074, -0.1278)}
# Invalid# locations = {[40.7128, -74.0060], [51.5074, -0.1278]} # TypeError4. Performance and memory
I compared memory usage:
import sys
my_list = [1, 2, 3, 4, 5]my_tuple = (1, 2, 3, 4, 5)
print(f"List: {sys.getsizeof(my_list)} bytes")print(f"Tuple: {sys.getsizeof(my_tuple)} bytes")# Output:# List: 120 bytes# Tuple: 80 bytesThe tuple uses 33% less memory. This adds up when you have millions of records.
Tuple creation is also faster—tuples don’t need to allocate extra space for potential growth like lists do.
When I still use lists
Despite the benefits of tuples, I still use lists when:
- I need to add/remove items frequently
- The data represents a collection of similar items
- I’m modifying data in place intentionally
# List is the right choice hereshopping_cart = ["apple", "banana", "orange"]shopping_cart.append("milk") # Modifying makes senseBeyond basic tuples: NamedTuple
When I need both immutability and readable field names, I use NamedTuple:
from collections import namedtuple
Color = namedtuple('Color', ['red', 'green', 'blue'])pixel = Color(255, 128, 0)
print(pixel.red) # 255 - clearer than pixel[0]print(pixel.green) # 128print(pixel.blue) # 0You get immutability with semantic field names. It’s the best of both worlds.
Visual comparison
┌─────────────────────────────────────────────────────────────┐│ LIST │├─────────────────────────────────────────────────────────────┤│ Type: Homogeneous (all same type) ││ Purpose: Collection of similar items ││ Mutable: Yes ││ Can be dict key: No ││ Memory: Higher (needs space for growth) ││ ││ ages = [25, 30, 35, 40] │└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐│ TUPLE │├─────────────────────────────────────────────────────────────┤│ Type: Heterogeneous (different types per position) ││ Purpose: Record where position has meaning ││ Mutable: No (immutable) ││ Can be dict key: Yes ││ Memory: Lower (fixed size) ││ ││ user = ("Alice", 30, True) ││ # (name: str, age: int, active: bool) │└─────────────────────────────────────────────────────────────┘Summary
In this post, I showed why Python tuple immutability matters in practical scenarios. The key point is that tuples aren’t just “immutable lists”—they serve a different purpose.
Use tuples when:
- Data should never change (enforcing constraints)
- Position has semantic meaning (records, not collections)
- You need dictionary keys or set elements
- You want better performance and lower memory usage
Use lists when:
- You need to modify the collection
- It’s a collection of similar items
- Mutation is intentional and documented
The immutability of tuples isn’t a limitation—it’s a feature that prevents bugs and makes code more predictable, especially in large codebases.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 What is the use of tuple over lists? (Reddit Discussion)
- 👨💻 Python Docs: Tuples and Sequences
- 👨💻 PEP 484 -- Type Hints
- 👨💻 Python's Mutable Default Argument Gotcha
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments