AlgoMaster Logo

Tuples Basics

High Priority26 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

A tuple is Python's container for a small, fixed group of values that belong together. The two numbers in a GPS coordinate, the three channels in an RGB color, the name and price of a product on a receipt: each of those is a tuple in spirit, and Python gives you a built-in type that matches the shape. This lesson covers what a tuple is, the several ways to create one (including the singleton case that catches every beginner), indexing, iteration, and the immutability rule with the one important caveat.

What a Tuple Is

A tuple is an ordered, immutable sequence of values. The word "sequence" should already feel familiar from lists. Order and indexing work the same way. The difference is the second word: a tuple cannot be changed after it's created.

Ordered means each item has a fixed position. If you write (40.7128, -74.0060) for a New York coordinate, the latitude is at position 0 and the longitude is at position 1. The order is part of the meaning, not an accident.

Immutable means once you build a tuple, its slots are frozen. You can't assign to point[0], you can't append to it, you can't sort it in place. To "change" a tuple, you build a new one. This is the same contract strings give you, just generalized to arbitrary values.

Here's a tuple in its simplest form:

Two numbers, stored in the order you wrote them. Python prints tuples with parentheses and comma-separated values, which is exactly how you usually write them in source code. Tuples are what you reach for when the group of values is small, the positions have specific meanings, and the whole thing should travel together as one unit.

A useful mental shorthand: lists are for "a bunch of things of the same kind" (a cart of products, prices in an order), and tuples are for "one structured record" (a coordinate, an RGB color, a row in a spreadsheet). That framing helps the rest of this lesson land.

Creating Tuples

There are several ways to make a tuple, and one of them has a sharp edge you need to know about up front.

The most common form is a tuple literal: values separated by commas, usually wrapped in parentheses.

() is the empty tuple. (255, 0, 0) packs three integers (red, green, blue). ("Wireless Mouse", 29.99, 142) packs a product name, its price, and its stock count into one record.

Here's the surprise: the parentheses are not actually what makes a tuple. It's the commas. Python is happy with this:

No parentheses, still a tuple. The comma is the signal that says "these values belong together as a sequence". Python adds parentheses when it prints, which is why the output still looks normal. Most Python developers write the parentheses anyway because the visual grouping makes the intent obvious, especially in arguments and return statements. But it's worth knowing that the commas are the real syntax.

That fact leads directly to the trickiest case in this section.

The Singleton Tuple Trap

To create a tuple with exactly one item, you need a trailing comma. Parentheses alone aren't enough.

("Wireless Mouse") is just the string "Wireless Mouse" with extra parentheses around it, the same way (1 + 2) is just the number 3. Parentheses are used everywhere in Python for grouping, so Python can't tell whether you meant "a tuple of one item" or "an expression I wrapped for clarity". The comma resolves the ambiguity:

The trailing comma is what makes it a tuple. Python even prints the comma when displaying single-element tuples to make this visible. Notice the output: ('Wireless Mouse',) with that lonely comma stuck on the end. That printed comma is your reminder that this is a one-element tuple, not a parenthesized string.

The same rule applies to numbers and any other value:

Empty tuples don't have this problem because there's nothing to confuse them with. () is unambiguously the empty tuple. The trap only exists for the one-item case.

The tuple() Constructor

The other way to make a tuple is the tuple() built-in. Called with no arguments, it returns an empty tuple:

tuple() and () produce the same empty tuple. Most code uses () because it's shorter and reads as "an empty tuple" at a glance. The constructor earns its keep when you pass it an iterable, anything Python can walk through one item at a time, the same idea you saw with list(). tuple(iterable) builds a tuple from those items:

tuple("RGB") walks the string one character at a time. tuple(range(5)) materializes a range into a real tuple. tuple([...]) copies a list's items into a tuple of the same items, which is useful when you want a frozen snapshot of a list that callers can't accidentally modify.

The same gotcha that hit list() hits tuple() too:

tuple("Mouse") iterates the string and gives you a tuple of single characters. ("Mouse",) wraps the whole string as a single item. Pick the literal form for everyday tuple creation. Reach for tuple(...) when you have an existing iterable you want to materialize into a tuple, often to freeze it.

Indexing and Negative Indexing

Indexing on a tuple works exactly the same way it does on a list. Position 0 is the first item, position 1 is the second, and negative indices count from the back.

product[0] is the name. product[1] is the price. product[-1] is the last item (the category), and product[-2] is one position before that (the stock count). The numbering scheme is identical to lists, both the positive and negative directions.

Out-of-range indices raise the same IndexError:

The error message says tuple index out of range instead of list index out of range, but the meaning is the same. The valid indices for a four-item tuple run from -4 to 3. Anything outside that range fails loudly.

Indexing on a tuple is O(1), just like a list. Tuples are also stored as a contiguous block of references, so jumping to position 5000 costs the same as jumping to position 0.

Length With len()

len() returns the number of items in a tuple. It works exactly the way it does on lists and strings:

Two items, three items, zero items. len() always returns an integer and never raises on a normal tuple. The cost is O(1) regardless of tuple size, because Python stores the length on the tuple object itself.

The length is also fixed for the lifetime of the tuple. A list's length can grow and shrink as you call append or pop. A tuple's length is set when you build it and stays the same forever. If you need a different size, you build a new tuple.

Iterating With for

Tuples are iterable, which means you can walk through them with a for loop the same way you walk a list or a string.

The variable channel takes on each value in turn. The loop body runs once per item.

enumerate() works on tuples too, just like it did on lists, when you want both the position and the value:

This pattern (pair each tuple item with a label by position) shows up often when you're dealing with records. There's a cleaner way to do it using named tuples, but the index-based version is a fine starting point.

You can also iterate a tuple of tuples, which is common when you have a list of records and don't need them mutable:

That for name, street, city in addresses: line is unpacking each inner tuple into three variables in one step. Unpacking is such a natural pairing with iteration that you'll see it everywhere.

Tuples Can Hold Mixed Types

Like lists, tuples don't care whether all the items are the same type. The whole point of a tuple is often to bundle related but differently-typed values.

A customer tuple holds a name (string), an age (int), an email (string), and a premium flag (bool). A product tuple holds a name, a price, and a stock count. An order line holds an order ID, a quantity, and a subtotal. Each position has a meaning, and the positions don't have to share a type.

This is part of why tuples feel different from lists in practice. A list of mixed types usually means "a record where I forgot to use a named structure". A tuple of mixed types usually means "a record on purpose, where the positions are stable and known."

Nested Tuples

A tuple can contain other tuples, just like a list can contain other lists. This is useful when you have a small, fixed structure with sub-structures inside it.

The order has an ID, a customer (a two-item tuple of name and email), and a tuple of items (each itself a two-item tuple of name and price). To dig into the structure, you index step by step:

order[1][0] is "the first item of the customer tuple" (the name). order[2][1][0] is "the second item in the items tuple, then its first item" (the name of the second product). Each [...] step drills one level deeper.

Nested tuples are convenient, but the readability falls off fast once you get past two levels of nesting. By the time you'd write data[2][1][3][0], the structure deserves real names. Dictionaries or named tuples will fit better.

Immutability: Why Tuples Reject Reassignment

The defining feature of a tuple is that you can't change it. The same cart[1] = "Ethernet Cable" pattern that works on lists fails on tuples:

Notice the error type: TypeError, not IndexError. Python isn't saying "that index doesn't exist", it's saying "tuples don't support this operation at all". The same message comes up for any in-place mutation attempt:

And tuples don't have the mutating methods that lists do. There's no tuple.append, no tuple.sort, no tuple.remove. They simply aren't part of the type.

To "change" a tuple, you build a new one. If a product's price needs updating, you make a fresh tuple with the new value and rebind the name:

The variable product now points to a different tuple. The original tuple still exists in memory for a moment (until Python garbage-collects it), but the name is now bound to the new one. This is the same pattern strings force on you. The cost is that you allocate a new object every time. The benefit is what comes next.

Immutability With Mutable Contents

Here's the caveat that trips people up: a tuple is immutable, but it doesn't make its contents immutable. If a tuple holds a reference to a mutable object (like a list), that inner object can still be mutated.

The tuple itself never changed. Position 0 still points to the string "ORD-1023". Position 1 still points to the same list it pointed to before. What changed is the list at position 1, which now has three items instead of two. The tuple's structure (which references it holds) is frozen. The objects it references are not.

This split looks subtle until you've seen it once. The diagram below shows what's actually going on:

The two arrows coming out of the tuple are the tuple's slots. Those arrows can never be repointed; that's what immutability means. But the list at the other end of one of those arrows is a normal mutable list, and append is allowed on it. The tuple has no idea the list grew; it just keeps pointing at the same list object.

In practice, this means a tuple is only fully "frozen" when all of its contents are themselves immutable (numbers, strings, other tuples, frozensets). A tuple of strings is completely frozen. A tuple containing a list is half-frozen: the structure is locked but the inner list can still change. This becomes important later when we talk about hashing and using tuples as dictionary keys, because only fully-immutable tuples are safe to hash.

Printing Tuples

Python prints tuples using the same parenthesized, comma-separated form you use to write them in source code:

A few details are worth noticing. Multi-item tuples print without a trailing comma. Single-item tuples print with a trailing comma, which is Python's way of reminding you that the value is a tuple rather than a parenthesized expression. The empty tuple prints as (), the same way you write it.

If you want a different formatted display, use an f-string and pull out the parts by position:

That's the same indexing as before, just used in a formatted context. It's a common pattern for displaying records: index the tuple by position, label each value in the output.

What's Wrong With This Code?

A common beginner mistake is reaching for an in-place modification on a tuple as if it were a list:

The fix depends on what you actually want. If the value really does need to change over time, the type should be a list, not a tuple:

If the value is fixed for the lifetime of one record but might be replaced wholesale (a product with a new price becomes a new product record), build a new tuple and rebind:

Picking between these is a design choice. The short version: use a tuple when the values stay put, use a list when you'll be modifying the collection over time.

A second common mistake is the singleton-tuple trap from earlier:

single here is a string, not a tuple, so iterating it walks the characters. The fix is the trailing comma:

One iteration, one item. The trailing comma is the difference between "a tuple holding one string" and "a string with redundant parentheses".

Quiz

Tuples Basics Quiz

10 quizzes