Skip to content

Lesson 06

Python Tuples

Immutable, ordered collections — when to reach for a tuple instead of a list, and what indexing actually means.

So far you’ve worked with single pieces of data: one string, one integer, one float. Real datasets aren’t that tidy. A correspondence record has a sender, a recipient, a date, and a place. A sentence is a sequence of words. A bibliography is a stack of citations. To handle this you need data structures — types that hold a collection of values.

Python’s three built-in collections are tuples, lists, and dictionaries. They split into two groups:

  • Mutable structures (lists, dictionaries) can be changed after creation. You can append, remove, and overwrite items in place.
  • Immutable structures (tuples) cannot. Once you’ve built one, its contents are fixed.

This lesson is about tuples; Lesson 07 covers lists.

Creating a tuple

A tuple is a comma-separated sequence of values inside parentheses:

a_tuple = (1, 1.0, "one")
print(a_tuple)

The items can be of any type, mixed freely — integers, floats, strings, even other tuples. Tuples can also nest, which is where they earn their keep:

coordinates = ((40.71, -74.01), (51.51, -0.13), (48.86, 2.35))
print(coordinates)

That’s three (latitude, longitude) pairs — New York, London, Paris — in a single object. You’ll see this shape over and over once you start working with structured data.

A tuple of one item needs a trailing comma to disambiguate it from a parenthesized expression:

not_a_tuple = (1)        # this is just the integer 1
yes_a_tuple = (1,)       # this is a tuple with one item
print(not_a_tuple, yes_a_tuple)

The parentheses are also optional in many positions — Python recognizes a tuple from the comma. This is how multiple-return values work:

def bounds():
    return 1815, 1852    # returns a tuple

start, end = bounds()    # unpacks the tuple into two names
print(start, end)

Indexing — accessing one item

To pull a single item out of a tuple, use square brackets and a position number. Python counts from 0. So the first item is at index 0, the second at 1, and so on.

a_tuple = (1, 1.0, "one")
print(a_tuple[0])    # 1
print(a_tuple[1])    # 1.0
print(a_tuple[2])    # "one"

Negative indexes count from the end:

a_tuple = (1, 1.0, "one")

print(a_tuple[-1])   # "one" — the last item

For nested structures, chain brackets together:

coordinates = ((40.71, -74.01), (51.51, -0.13), (48.86, 2.35))
print(coordinates[0])      # (40.71, -74.01) — the New York pair
print(coordinates[0][0])   # 40.71            — its latitude
print(coordinates[-1][1])  # 2.35             — Paris's longitude

Read it left to right: “first pair, first item.” Once you can navigate nested structures comfortably, the rest of Python opens up.

Slicing — accessing a range

You can also pull out a sub-section using a colon between two indexes:

a_tuple = ("first", "second", "third", "fourth", "fifth")
print(a_tuple[1:3])    # ("second", "third")
print(a_tuple[:2])     # ("first", "second") — from the start
print(a_tuple[2:])     # ("third", "fourth", "fifth") — to the end

The slice goes from the first index up to but not including the second. This works on lists and strings too — it’s a single, consistent rule.

When to reach for a tuple

Lists are more common, so when do you actually want a tuple? Three cases come up regularly:

  1. A small group of related values you don’t expect to change. A coordinate, a (year, place) pair, a (start, end) range. Using a tuple signals “this is one record” rather than “this is a list I might grow.”
  2. A return value with multiple parts. Functions return tuples to hand back several values at once.
  3. A dictionary key or a set member. Mutable types like lists can’t be used as keys or stored in sets. Tuples can. If you ever need to count how many times each (year, place) pair appears in a dataset, the pair has to be a tuple.

A tuple’s immutability is its feature, not a limitation. It tells future readers — including future you — that this collection is fixed and safe to share around the program.

If you’re comfortable with these concepts, continue to Lesson 07: Python Lists.

Running the code

Save any snippet from this lesson to a file — say try.py — and run it from your project folder:

uv run try.py

uv run uses the project’s Python and dependencies automatically; no virtualenv to activate. If you haven’t set the project up yet, Lesson 01 walks through it.