What is the difference between List and Tuple?
Compare Python lists and tuples: mutability, memory, hashability, unpacking, use cases in AI/ML code, and when to choose each.
The Core Difference: Mutability
A list is mutable ā you can change it after creation. A tuple is immutable ā once created, it cannot change.
# List: mutable
drugs = ["warfarin", "aspirin", "metformin"]
drugs.append("lisinopril") # OK
drugs[0] = "heparin" # OK
drugs.remove("aspirin") # OK
# Tuple: immutable
vitals = (120, 80, 98.6) # systolic, diastolic, temp
vitals[0] = 130 # TypeError: 'tuple' object does not support item assignment
vitals.append(72) # AttributeError: 'tuple' has no 'append'Syntax
# List: square brackets
medications = ["warfarin", "aspirin"]
# Tuple: parentheses (or just commas)
coordinates = (40.7128, -74.0060) # lat, lon
coordinates = 40.7128, -74.0060 # Same tuple ā parens optional
# Single-element tuple ā the trailing comma is required
single = (42,) # tuple
not_tuple = (42) # Just the int 42, wrapped in parens
print(type(single)) # <class 'tuple'>
print(type(not_tuple)) # <class 'int'>
# Empty
empty_list = []
empty_tuple = ()Memory and Performance
Tuples are more memory-efficient than lists:
import sys
list_data = [1, 2, 3, 4, 5]
tuple_data = (1, 2, 3, 4, 5)
print(sys.getsizeof(list_data)) # 104 bytes (Python overhead for dynamic sizing)
print(sys.getsizeof(tuple_data)) # 80 bytes (fixed allocation)
# Tuple creation is also faster
import timeit
list_time = timeit.timeit("[1, 2, 3, 4, 5]", number=10_000_000)
tuple_time = timeit.timeit("(1, 2, 3, 4, 5)", number=10_000_000)
print(f"List: {list_time:.2f}s | Tuple: {tuple_time:.2f}s")
# Tuple is roughly 5-10% faster ā matters in tight loopsHashability: Tuples as Dict Keys
Lists are not hashable ā you cannot use them as dictionary keys. Tuples are hashable (if their contents are hashable):
# Lists cannot be dict keys
interaction_db = {}
interaction_db[["warfarin", "aspirin"]] = "Major" # TypeError: unhashable type 'list'
# Tuples can be dict keys
interaction_db = {}
interaction_db[("warfarin", "aspirin")] = "Major" # Works
interaction_db[("metformin", "contrast")] = "Major"
key = ("warfarin", "aspirin")
print(interaction_db[key]) # "Major"
# Tuples in sets (same reason)
seen_pairs: set = set()
seen_pairs.add(("warfarin", "aspirin")) # Works
seen_pairs.add(["warfarin", "aspirin"]) # TypeErrorUnpacking
Both types support unpacking, but tuple unpacking is more idiomatic:
# Tuple unpacking ā natural for fixed-structure data
systolic, diastolic, temp = (120, 80, 98.6)
lat, lon = 40.7128, -74.0060
# Extended unpacking with *
first, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [2, 3, 4, 5]
*beginning, last = [1, 2, 3, 4, 5]
print(beginning) # [1, 2, 3, 4]
print(last) # 5
first, *middle, last = [1, 2, 3, 4, 5]
print(middle) # [2, 3, 4]
# Common in AI: unpacking return values
def train_step(batch) -> tuple[float, float]:
return 0.342, 0.891 # loss, accuracy
loss, accuracy = train_step(batch)
print(f"Loss: {loss:.3f} | Accuracy: {accuracy:.3f}")
# Swap without a temp variable
a, b = 1, 2
a, b = b, a # Tuple unpacking under the hood
print(a, b) # 2 1Converting Between Them
medications = ["warfarin", "aspirin", "metformin"]
# List ā Tuple (make immutable)
med_tuple = tuple(medications)
# Tuple ā List (make mutable)
med_list = list(med_tuple)
# Common pattern: receive as tuple (immutable config), convert to list to modify
def process_drug_list(drugs: tuple[str, ...]) -> list[str]:
"""Process a fixed set of drugs, returning a filtered list."""
working = list(drugs) # Convert to list for mutation
working = [d for d in working if d != "aspirin"]
return workingWhen to Use Each
# Use LIST when:
# - Content changes over time (building results)
batch_results: list[float] = []
for item in dataset:
score = evaluate(item)
batch_results.append(score) # Growing list
# - Order-independent elements
active_drugs = ["warfarin", "metformin"]
active_drugs.append("lisinopril")
active_drugs.remove("warfarin")
# Use TUPLE when:
# - Data is fixed / shouldn't change (coordinates, RGB colors, settings)
ORIGIN = (0.0, 0.0)
API_VERSIONS = ("v1", "v2", "v3")
# - Multiple return values from a function
def get_patient_stats(patient_id: str) -> tuple[float, float, int]:
return 2.4, 120.5, 67 # INR, weight_kg, age
inr, weight, age = get_patient_stats("P001")
# - Dict keys or set members
interaction_seen: set[tuple[str, str]] = set()
interaction_seen.add(("warfarin", "aspirin"))
# - As function arguments that shouldn't be modified
ALLOWED_ROUTES = ("PO", "IV", "IM", "SC", "SL") # Immutable config
def validate_route(route: str) -> bool:
return route in ALLOWED_ROUTESNamed Tuples: Best of Both Worlds
When you want tuple immutability but dict-like field access:
from collections import namedtuple
# Classic namedtuple
PatientVitals = namedtuple("PatientVitals", ["systolic", "diastolic", "temp_f", "spo2"])
vitals = PatientVitals(systolic=120, diastolic=80, temp_f=98.6, spo2=98)
print(vitals.systolic) # 120 ā named access
print(vitals[0]) # 120 ā still a tuple, index access works
print(vitals._asdict()) # OrderedDict with all fields
# Preferred: dataclass (covered separately) or typing.NamedTuple
from typing import NamedTuple
class DrugInteraction(NamedTuple):
drug_a: str
drug_b: str
severity: str
mechanism: str
interaction = DrugInteraction(
drug_a="warfarin",
drug_b="aspirin",
severity="Major",
mechanism="Additive anticoagulation and antiplatelet effects",
)
print(interaction.severity) # "Major"
print(interaction) # DrugInteraction(drug_a='warfarin', ...)Summary
| Feature | list | tuple |
|---|---|---|
| Syntax | [a, b, c] | (a, b, c) |
| Mutable | Yes | No |
| Hashable | No | Yes (if contents hashable) |
| Memory | More | Less |
| Speed | Slightly slower to create | Slightly faster to create |
| Dict key / set member | No | Yes |
| Typical use | Growing collections | Fixed records, return values, dict keys |
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.