Creating tuples
A tuple is a sequence of values grouped together. Unlike lists, you cannot add, remove, or change items after creation. That constraint is the whole point.
The most common syntax uses parentheses, but the parentheses are not what makes a tuple. The comma is what makes a tuple. Parentheses are just punctuation that improves readability.
1# Standard syntax2point = (3, 7)3rgb = (255, 128, 0)4empty = ()56# Parentheses are optional, the comma creates the tuple7point = 3, 7 # same as (3, 7)8x = 1, 2, 3 # same as (1, 2, 3)910print(type(point)) # <class 'tuple'>11print(type(x)) # <class 'tuple'>
The single-element gotcha
Creating a tuple with exactly one item trips up almost everyone the first time. Writing (42) gives you the integer 42, not a tuple. The parentheses are just grouping. You need the trailing comma.
1not_a_tuple = (42) # just the integer 422single = (42,) # a tuple containing 423also_single = 42, # same thing, no parens needed45print(type(not_a_tuple)) # <class 'int'>6print(type(single)) # <class 'tuple'>7print(single) # (42,)8print(len(single)) # 1
Python always displays single-element tuples with the trailing comma: (42,). That is a reminder that the comma is the real syntax, not the parentheses.
The tuple() constructor
tuple() converts any iterable into a tuple. This is how you freeze a list, range, or string into an immutable sequence.
1from_list = tuple([1, 2, 3]) # (1, 2, 3)2from_range = tuple(range(5)) # (0, 1, 2, 3, 4)3from_str = tuple("abc") # ('a', 'b', 'c')4from_set = tuple({3, 1, 2}) # (1, 2, 3): order not guaranteed56print(from_list) # (1, 2, 3)7print(from_range) # (0, 1, 2, 3, 4)8print(from_str) # ('a', 'b', 'c')
Immutability
A tuple is immutable. Once created, you cannot change its contents. You cannot add items, remove items, or replace an item at a given index. Any attempt raises a TypeError.
1t = (1, 2, 3)23t[0] = 99 # TypeError: 'tuple' object does not support item assignment4t.append(4) # AttributeError: 'tuple' object has no attribute 'append'5del t[0] # TypeError: 'tuple' object doesn't support item deletion
Reading, slicing, and searching all work the same as with lists.
1t = (10, 20, 30, 40, 50)23print(t[0]) # 104print(t[-1]) # 505print(t[1:3]) # (20, 30)6print(30 in t) # True7print(len(t)) # 58print(t.count(20)) # 19print(t.index(40)) # 3
The mutable-inside gotcha
A tuple cannot be changed, but if it contains a mutable object, that object can still be changed. The tuple holds a reference to the object, not a frozen copy of it. This surprises people, and it is worth understanding clearly.
1t = ([1, 2], [3, 4]) # a tuple of two lists23# The tuple itself is immutable4# t[0] = [9, 9] # TypeError: can't reassign the reference56# But the list inside can be mutated7t[0].append(99)8print(t) # ([1, 2, 99], [3, 4])9print(t[0]) # [1, 2, 99]
Immutability means the tuple's references cannot change. The objects those references point to follow their own rules. A tuple of lists is not deeply frozen.
Try the operations below to see exactly what a tuple allows and what it blocks.
Packing and unpacking
Putting values into a tuple is called packing. Pulling them back out into separate variables is called unpacking. Both happen in a single assignment line and both are used constantly in real Python code.
1# Packing2point = 3, 7 # packs 3 and 7 into a tuple34# Unpacking5x, y = point # assigns 3 to x and 7 to y6print(x, y) # 3 778# Pack and unpack in one line9x, y = 3, 7 # the right side creates a temporary tuple10print(x, y) # 3 7
Swap without a temp variable
The classic interview trick. Python evaluates the right side completely before doing any assignment, so this works cleanly with no temporary variable needed.
1a, b = 10, 202print(a, b) # 10 2034a, b = b, a # right side (b, a) = (20, 10) is packed first, then unpacked5print(a, b) # 20 10
Starred unpacking
The * prefix in an unpacking assignment collects all remaining items into a list. It can go anywhere in the assignment, not just at the end.
1nums = (1, 2, 3, 4, 5)23first, *rest = nums4print(first) # 15print(rest) # [2, 3, 4, 5]67*init, last = nums8print(init) # [1, 2, 3, 4]9print(last) # 51011first, *middle, last = nums12print(middle) # [2, 3, 4]
Starred unpacking always produces a list, even when collecting from a tuple. Only one starred variable is allowed per unpacking expression.
Ignoring values with _
When a tuple has values you do not need, assign them to _. It is a legal variable name but signals to any reader that the value is intentionally discarded.
1# Only care about the score, not name or city2_, score, _ = ("Alice", 95, "NYC")3print(score) # 9545# Unpack a 3-tuple but only use the first value6x, *_ = (10, 20, 30, 40)7print(x) # 10
See it live. Pick a pattern below and watch how the tuple items map to variables.
Named tuples
A plain tuple forces you to remember that index 0 is the x coordinate and index 1 is the y coordinate. A named tuple gives each position a name so you can write p.x instead of p[0]. It is still a tuple underneath and has no extra memory cost.
collections.namedtuple
1from collections import namedtuple23Point = namedtuple("Point", ["x", "y"])45p = Point(3, 7)6print(p) # Point(x=3, y=7)7print(p.x) # 38print(p[0]) # 3: index access still works9print(p.y) # 71011# It is a real tuple12print(isinstance(p, tuple)) # True13print(len(p)) # 214x, y = p # unpacking works too
typing.NamedTuple (preferred)
The modern way uses class syntax with type annotations. It reads more clearly, supports default values, and plays well with type checkers.
1from typing import NamedTuple23class Point(NamedTuple):4 x: float5 y: float6 label: str = "" # default value, optional field78p1 = Point(3.0, 7.0)9p2 = Point(x=1.5, y=2.5, label="origin")1011print(p1) # Point(x=3.0, y=7.0, label='')12print(p2.label) # origin1314# Methods work normally15def distance(p: Point) -> float:16 return (p.x ** 2 + p.y ** 2) ** 0.51718print(distance(p1)) # 7.615773105863909
_replace() for immutable updates
You cannot modify a named tuple in place, but _replace() returns a new instance with specific fields changed. The original is untouched.
1from typing import NamedTuple23class Employee(NamedTuple):4 name: str5 dept: str6 salary: int78alice = Employee("Alice", "Engineering", 90000)9promoted = alice._replace(salary=110000, dept="Engineering Lead")1011print(alice) # Employee(name='Alice', dept='Engineering', salary=90000)12print(promoted) # Employee(name='Alice', dept='Engineering Lead', salary=110000)
Named tuple vs dataclass
If you need immutability and interoperability with tuple operations (unpacking, iteration, len), use a named tuple. If you need mutability, inheritance, or custom methods, use a dataclass. Named tuples use less memory and have zero overhead compared to a plain tuple.
Explore field access below. Click any field to see both the name-based and index-based access side by side.
Hashability
A hashable object has a stable integer identity that never changes during its lifetime. Python requires this for any object used as a dictionary key or placed in a set. Lists are not hashable because they can change. Tuples are hashable because they cannot.
1# Tuples can be dict keys2grid = {3 (0, 0): "origin",4 (1, 0): "right",5 (0, 1): "up",6}7print(grid[(1, 0)]) # right89# Lists cannot10# bad = {[0, 0]: "origin"} # TypeError: unhashable type: 'list'1112# Tuples can be in sets13visited = set()14visited.add((3, 5))15visited.add((1, 2))16print((3, 5) in visited) # True
Practical: graph traversal
The most common use of tuple hashability is tracking visited positions in a grid or graph. A set of tuples gives you O(1) membership tests with readable code.
1def bfs(start: tuple, grid: set) -> list:2 from collections import deque34 visited = {start}5 queue = deque([start])6 order = []78 while queue:9 x, y = queue.popleft()10 order.append((x, y))1112 for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:13 neighbor = (x + dx, y + dy)14 if neighbor in grid and neighbor not in visited:15 visited.add(neighbor)16 queue.append(neighbor)1718 return order1920cells = {(0,0), (0,1), (1,0), (1,1), (2,0)}21print(bfs((0, 0), cells))
When tuples are not hashable
A tuple is only hashable if all of its elements are hashable. Put a list inside and the whole tuple becomes unhashable.
1hash((1, 2, 3)) # works fine2hash((1, "hello", True)) # works fine34# A tuple containing a list is not hashable5t = (1, [2, 3])6hash(t) # TypeError: unhashable type: 'list'
If you need a hashable fixed-length sequence with mutable elements, freeze them first. For example, convert the inner list to a tuple: (1, tuple([2, 3])) is hashable.
Tuples vs lists
Both are sequences. The difference is intent. Use a list when you have a collection of items that can grow, shrink, or be reordered. Use a tuple when you have a fixed structure where each position has a specific meaning.
List
A collection of items of the same kind. The number of items can change. Position is arbitrary.
users = ['Alice', 'Bob', 'Carol']Tuple
A record where each position has fixed meaning. The structure is part of the design.
point = (x, y)Returning multiple values
Functions that return multiple values return a tuple. Python packs the return values automatically. The caller unpacks them. This is one of the most common tuple patterns in real code.
1def min_max(nums):2 return min(nums), max(nums) # returns a tuple34lo, hi = min_max([3, 1, 4, 1, 5, 9, 2, 6])5print(lo, hi) # 1 967# Works with any iterable8def split_name(full_name: str) -> tuple[str, str]:9 parts = full_name.split(" ", 1)10 return parts[0], parts[1] if len(parts) > 1 else ""1112first, last = split_name("Ada Lovelace")13print(first, last) # Ada Lovelace
Structured records
When data has a fixed schema, a tuple makes that structure explicit. A database row, a coordinate, a color channel, a key-value pair. All are natural tuples.
1# Database rows2rows = [3 ("Alice", "Engineering", 90000),4 ("Bob", "Design", 80000),5 ("Carol", "Product", 95000),6]78for name, dept, salary in rows:9 print(f"{name} ({dept}): {salary}")1011# Coordinates on a grid12DIRECTIONS = {13 "up": (0, -1),14 "down": (0, 1),15 "left": (-1, 0),16 "right": (1, 0),17}
Signal immutability to the reader
Choosing a tuple is also a signal. When a function returns a tuple, the caller knows the structure is fixed. When a constant is a tuple, it is clear that nobody should add to or modify it. The choice communicates intent even before anyone reads the code.
1# Constants as tuples, clearly not meant to grow2WEEKDAYS = ("Mon", "Tue", "Wed", "Thu", "Fri")3HTTP_OK = (200, 201, 202, 204)4RGB_BLACK = (0, 0, 0)56# Using a list here would mislead the reader into thinking7# this collection is meant to be modified