A tuple of tuples in Python is a tuple whose items are themselves tuples, so you get rows of fixed values you can’t change. You build one with nested parentheses and read it with two indexes:
cities = (("Austin", "TX"), ("Denver", "CO"))
cities[1][0] # 'Denver'
These examples use a small table of US cities, and the screenshots come from Python 3.12.5 running in a Windows Command Prompt.
I’ll cover creating and indexing nested tuples, looping with unpacking, converting to and from lists, why they’re hashable, and sorting and flattening them.
How to create a tuple of tuples in Python
Write each row as its own tuple and wrap them all in an outer pair of parentheses. The first index picks the row, the second picks the field:
cities = (
("Austin", "TX", 979_882),
("Denver", "CO", 716_577),
("Boston", "MA", 653_833),
)
print(cities[1]) # the second inner tuple
print(cities[1][0]) # first item of the second tuple
print(cities[-1][2]) # last city's population
print(len(cities), "rows,", len(cities[0]), "fields each")
one_row = (("Austin", "TX"),) # the trailing comma makes it a tuple of tuples
not_nested = (("Austin", "TX")) # without it, the outer parentheses do nothing
print(len(one_row), len(not_nested))
Output:
('Denver', 'CO', 716577)
Denver
653833
3 rows, 3 fields each
1 2

cities[1][0] is Denver. The last line shows why a one-row tuple needs a trailing comma.That last check trips people up. (("Austin", "TX")) is just a plain two-item tuple, because parentheses alone don’t make a tuple; the comma does.
Looping through a tuple of tuples
Because every row has the same shape, you can unpack each inner tuple straight into named variables in the for line:
cities = (
("Austin", "TX", 979_882),
("Denver", "CO", 716_577),
("Boston", "MA", 653_833),
)
for city, state, population in cities:
print(f"{city:<8} {state} {population:>9,}")
for i, (city, state, _) in enumerate(cities, start=1):
print(i, f"{city}, {state}")
Output:
Austin TX 979,882
Denver CO 716,577
Boston MA 653,833
1 Austin, TX
2 Denver, CO
3 Boston, MA

row[0], row[1] and row[2].Use _ for a field you don’t need. The Python for loop guide covers enumerate() and unpacking in more depth.
Converting a list of lists to a tuple of tuples
Data often arrives as a list of lists, from a CSV file or JSON. Converting each row with tuple() freezes it, and the reverse gives you something you can edit:
rows = [["Austin", "TX"], ["Denver", "CO"]] # a list of lists, e.g. from a CSV
frozen = tuple(tuple(row) for row in rows)
print(frozen)
back = [list(row) for row in frozen] # and back again when you need to edit
back[0][0] = "Dallas"
print(back)
pairs = (("TX", 0.0625), ("CO", 0.029))
print(dict(pairs)) # two-item tuples convert straight to a dict
Output:
(('Austin', 'TX'), ('Denver', 'CO'))
[['Dallas', 'TX'], ['Denver', 'CO']]
{'TX': 0.0625, 'CO': 0.029}
Tuples of two-item tuples are also exactly what dict() expects, which is handy for lookup tables like state sales-tax rates. Converting a dictionary to a list of tuples goes the other way.
Why a tuple of tuples can’t be changed
Neither the outer tuple nor the inner ones support item assignment. That’s the point: it also makes the whole structure hashable, so it can be a dictionary key or a set member:
grid = ((1, 2), (3, 4))
try:
grid[0][1] = 99
except TypeError as err:
print("TypeError:", err)
lookup = {grid: "2x2 block"} # hashable, so it works as a dict key
print(lookup[((1, 2), (3, 4))])
mixed = ((1, 2), [3, 4]) # one inner list spoils it
try:
hash(mixed)
except TypeError as err:
print("TypeError:", err)
Output:
TypeError: 'tuple' object does not support item assignment
2x2 block
TypeError: unhashable type: 'list'

Immutability only goes as deep as the tuples do. Put a list inside and that list can still change, so the outer tuple loses its hash. Mutable vs immutable in Python explains the rule.
Sorting and searching a tuple of tuples
sorted(), max() and in all work on nested tuples. A key function tells them which field to look at:
cities = (
("Austin", "TX", 979_882),
("Denver", "CO", 716_577),
("Boston", "MA", 653_833),
)
by_size = sorted(cities, key=lambda row: row[2], reverse=True)
print(by_size[0][0], "is the largest")
print(max(cities, key=lambda row: row[2])[0])
print(("Denver", "CO", 716_577) in cities)
print([city for city, state, _ in cities if state == "MA"])
Output:
Austin is the largest
Austin
True
['Boston']
sorted() returns a list, since a tuple can’t be reordered in place. Wrap it in tuple() if you want a tuple back. Sorting a list of tuples has more key examples.
How do you flatten a tuple of tuples?
A nested comprehension or itertools.chain turns the rows into one flat sequence. zip(*grid) transposes it instead:
from itertools import chain
grid = ((1, 2, 3), (4, 5, 6))
print([n for row in grid for n in row])
print(tuple(chain.from_iterable(grid)))
print(sum(sum(row) for row in grid))
print(tuple(zip(*grid))) # swap rows and columns
Output:
[1, 2, 3, 4, 5, 6]
(1, 2, 3, 4, 5, 6)
21
((1, 4), (2, 5), (3, 6))

Tuple of tuples vs list of lists
| Tuple of tuples | List of lists | |
|---|---|---|
| Change a value | No | Yes |
| Use as a dict key or in a set | Yes | No |
| Good for | Fixed reference data, coordinates, lookup tables | Data you’ll edit or append to |
| Convert | tuple(map(tuple, rows)) | [list(r) for r in rows] |
I reach for a tuple of tuples when the data shouldn’t change while the program runs, like a list of states and their tax rates. If you’ll be appending or editing rows, a list of lists is the better fit.
Keep going with tuples:
- Create a tuple in Python
- Access tuple elements
- Unpack a tuple in Python
- Iterate through tuples
- Set vs tuple in Python
Frequently asked questions
What is a tuple of tuples in Python?
A tuple whose items are tuples, such as ((1, 2), (3, 4)). It works like a small, read-only table of rows.
How do I access an element in a tuple of tuples?
Use two indexes: the first picks the inner tuple and the second picks the item, so t[1][0] is the first item of the second row.
How do I make a tuple with a single tuple inside?
Add a trailing comma: (("Austin", "TX"),). Without the comma the outer parentheses are ignored.
Can I change a value in a tuple of tuples?
No. Convert it to a list of lists, change the value, and convert it back with tuple(tuple(r) for r in rows).
Is a tuple of tuples hashable?
Yes, as long as every inner item is hashable. That means it can be a dictionary key or go in a set. The Python tutorial on tuples covers the basics.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn