---
title: Tuples in Python
source: https://app.sythra.ai/learn/python/tuples-in-python
topic: Python
updated: 2026-08-12
publisher: Sythra (https://app.sythra.ai)
---

# Tuples in Python

A Python tuple is an immutable, ordered sequence created with commas, e.g. (1, 2, 3). Tuples are used to return multiple values from a function, unpack values with a, b = b, a, and as dictionary keys since they're hashable — unlike lists.

_Source: [https://app.sythra.ai/learn/python/tuples-in-python](https://app.sythra.ai/learn/python/tuples-in-python) — free to read on Sythra._

## Key points

- Tuples are immutable — like a list you can never change after creation
- A one-element tuple needs a trailing comma: ('a',) not ('a')
- a, b = b, a swaps variables with no temporary variable
- Functions return multiple values by returning a tuple
- *args gathers arguments into a tuple; * scatters a tuple into arguments
- Tuples (not lists) can be used as dictionary keys

Lists can hold anything and can be changed freely. Strings hold characters and can never be changed. A **tuple** sits exactly in the middle: a sequence like a list, but immutable like a string. Once created, it cannot change — a promise that Python rewards by letting tuples do things lists can't, like act as dictionary keys.

This page covers creating tuples, the classic one-element trap, the elegant tuple-swap, returning multiple values from a function, `*args`, `zip()` and `enumerate()`, and the DSU sorting pattern. Pair it with [Lists in Python](/learn/python/lists-in-python) and [Dictionaries in Python](/learn/python/dictionaries-in-python) for how all three sequence types interact.

## What you will learn

By the end you can:

- Create tuples and avoid the one-element tuple trap
- Swap variables and unpack sequences with tuple assignment
- Return multiple values from a function using a tuple
- Write functions that accept any number of arguments with `*args`
- Use `zip()` and `enumerate()`, and use tuples as dictionary keys

## Tuples are immutable

A tuple is created with a comma-separated list of values. Parentheses are optional but conventional:

```python
t = 'a', 'b', 'c', 'd', 'e'       # valid, no parentheses
t = ('a', 'b', 'c', 'd', 'e')     # same thing, with parentheses
```

Index and slice a tuple exactly like a list:

```python
t = ('a', 'b', 'c', 'd', 'e')
print(t[0])      # 'a'
print(t[1:3])    # ('b', 'c')
```

But try to change an element and Python refuses:

```python
t[0] = 'A'
# TypeError: 'tuple' object does not support item assignment
```

You can build a brand-new tuple from pieces of the old one — you are constructing something new, not modifying anything:

```python
t = ('A',) + t[1:]
print(t)   # ('A', 'b', 'c', 'd', 'e')
```

### Three ways to create a tuple

```python
t = tuple()          # empty tuple: ()
t = tuple('lupins')  # from a string: ('l', 'u', 'p', 'i', 'n', 's')
t = ('a', 'b', 'c')  # literal
t = 'a', 'b', 'c'    # no parentheses (works, less readable)
```

### The one-element tuple trap

One of Python's most famous gotchas: a single value in parentheses is **not** automatically a tuple — it is just that value with parentheses around it doing nothing. A genuine one-element tuple needs a trailing comma:

```python
t1 = ('a',)     # this IS a tuple
t2 = ('a')      # this is NOT a tuple — it's just a string
t3 = 'a',       # also a valid one-element tuple
```

> **Debugging tip:** 

## Tuple assignment — the elegant swap

Swapping two variables traditionally needs a temporary variable:

```python
temp = a
a = b
b = temp
```

Tuple assignment collapses this into one line:

```python
a, b = b, a
```

The right side is fully evaluated first — both `b` and `a` are read and packaged up before anything is assigned. Only then are the values handed to the left side, in order. No temporary variable, ever. The number of variables on the left must match the number of values on the right:

```python
a, b = 1, 2, 3
# ValueError: too many values to unpack (expected 2)
```

The right side doesn't have to be a tuple at all — any sequence works, including the return value of a function like `.split()`:

```python
addr = 'monty@python.org'
uname, domain = addr.split('@')
print(uname)    # monty
print(domain)   # python.org
```

Reads almost like English: _"Split the address at the @ symbol, and give me the username and the domain."_

## Returning multiple values from a function

A function can only ever return one value — but if that value is a tuple, the caller can unpack several results at once. The built-in `divmod()` does exactly this:

```python
t = divmod(7, 3)
print(t)   # (2, 1)

quot, rem = divmod(7, 3)
print(quot)    # 2
print(rem)     # 1
```

Write your own functions the same way — no parentheses required to build a tuple to return:

```python
def min_max(t):
    return min(t), max(t)

lo, hi = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print(lo)    # 1
print(hi)    # 9
```

Anytime a function naturally produces two or more related outputs, returning them together as a tuple is more honest than stuffing them into a list, and cleaner than calling the function twice.

## Variable-length arguments: *args

Sometimes a function should accept any number of arguments. A parameter starting with `*` gathers all remaining arguments into a single tuple:

```python
def printall(*args):
    print(args)

printall(1, 2.0, '3')
# (1, 2.0, '3')
```

This is called **gathering**. **Scatter** is the opposite — spreading a tuple back out into separate arguments when calling a function. Passing a tuple directly where two arguments are expected fails:

```python
t = (7, 3)
divmod(t)
# TypeError: divmod expected 2 arguments, got 1
```

Put a `*` in front and Python scatters it into separate arguments:

```python
divmod(*t)
# (2, 1)
```

Gather (`*args` in a function definition) and scatter (`*` when calling) are exact opposites. Combine `*args` with `sum()` to sum any number of arguments:

```python
def sumall(*args):
    return sum(args)

sumall(1, 2, 3)         # 6
sumall(10, 20, 30, 40)  # 100
```

## zip() and enumerate()

`zip()` pairs two or more sequences together, element by element, by position:

```python
s = 'abc'
t = [0, 1, 2]
list(zip(s, t))
# [('a', 0), ('b', 1), ('c', 2)]
```

> **Iterators in Python 3:** 

If the sequences have different lengths, `zip()` stops at the shorter one:

```python
list(zip('Anne', 'Elk'))
# [('A', 'E'), ('n', 'l'), ('n', 'k')]
# 'Anne' has 4 letters, 'Elk' has 3 — only 3 pairs
```

Combine `zip()` with tuple assignment in a `for` loop to compare two sequences side by side with no manual indexing:

```python
def has_match(t1, t2):
    for x, y in zip(t1, t2):
        if x == y:
            return True
    return False
```

`enumerate()` gives you both the **index** and the **value** while traversing a sequence:

```python
for index, element in enumerate('abc'):
    print(index, element)
# 0 a
# 1 b
# 2 c
```

Reach for `enumerate()` anytime you catch yourself writing `for i in range(len(t)): element = t[i]` — `enumerate()` does the same thing more cleanly.

## Dictionaries and tuples together

Lists can't be dictionary keys because they're mutable and unhashable. Tuples can — this opens up genuinely useful designs. `.items()` returns key-value pairs as tuples:

```python
d = {'a': 0, 'b': 1, 'c': 2}
t = list(d.items())
print(t)
# [('a', 0), ('b', 1), ('c', 2)]
```

Going the other direction, build a dictionary from a list of tuples with `dict()` — and combine it with `zip()` for a one-liner:

```python
t = [('a', 0), ('b', 1), ('c', 2)]
d = dict(t)

d = dict(zip('abc', range(3)))
print(d)   # {'a': 0, 'b': 1, 'c': 2}
```

Because tuples are hashable, they work as dictionary keys — useful whenever you need to index by a _combination_ of values. A phone directory keyed by (last name, first name):

```python
directory = {}
directory['Cleese', 'John'] = '08700 100 222'
directory['Palin', 'Michael'] = '08700 100 222'

for last, first in directory:
    print(first, last, directory[last, first])
```

`directory[last, first]` is exactly equivalent to `directory[(last, first)]` — it's the **comma** that creates the tuple, not the parentheses.

## Comparing tuples and the DSU sorting pattern

Tuples support `<`, `>`, `==`, comparing **element by element, left to right**. The first pair that differs decides the result:

```python
(0, 1, 2) < (0, 3, 4)          # True — 0==0, then 1<3, decided
(0, 1, 2000000) < (0, 3, 4)    # True — 2000000 never even checked
```

This is exactly how sorting a list of tuples works too — primarily by the first element, breaking ties with the second, and so on. This enables a powerful sorting technique called **DSU: Decorate, Sort, Undecorate**. Pair each element with its sort key, sort those pairs, then strip the keys back off:

```python
def sort_by_length(words):
    t = []
    for word in words:
        t.append((len(word), word))    # decorate

    t.sort(reverse=True)               # sort

    res = []
    for length, word in t:             # undecorate
        res.append(word)
    return res

print(sort_by_length(['banana', 'fig', 'cherry', 'kiwi']))
# ['banana', 'cherry', 'kiwi', 'fig']
```

DSU is the go-to pattern whenever Python's default sort order isn't what you need — the decorate step lets you attach any sort key at all.

## Choosing between strings, lists, and tuples

| Type | Mutable? | Use when |
| --- | --- | --- |
| str | No | Working with text; convert to a list if you need to edit characters |
| list | Yes | Adding/removing/changing elements; building a collection gradually |
| tuple | No | Returning multiple values, using a sequence as a dict key, or guaranteeing data won't be modified |

Because tuples are immutable, they lack in-place methods like `.sort()`. But `sorted()` and `reversed()` both work fine on them — they return brand-new lists instead of modifying the original. Rule of thumb: if the collection changes over time, use a list; if it represents a fixed record or a compound dictionary key, use a tuple.

## A preview: named tuples

Regular tuples are accessed by position — `t[0]`, `t[1]` — which isn't always readable. `namedtuple` gives each position an actual name:

```python
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)

print(p.x)    # 3
print(p.y)    # 4
print(p[0])   # 3 — still works by index too
```

A named tuple is still completely immutable, but its fields have readable names — the safety of a tuple with the readability of an object. Great for fixed-structure records like coordinates or RGB colors.

## Common mistakes

- Writing `('a')` expecting a tuple — it's just a string; you need the trailing comma `('a',)`
- Trying to assign into a tuple like `t[0] = 'A'` — tuples are immutable
- Passing a tuple where separate arguments are expected without unpacking it with `*`
- Confusing a list of tuples with a list of lists — they print similarly but behave very differently
- Forgetting that tuple comparison stops at the first differing element

## Why this matters

Tuples show up constantly once you start writing real Python: function return values, dictionary keys, coordinates, and the gather/scatter mechanics behind flexible function signatures. Knowing exactly when immutability helps — and when it gets in your way — is what separates code that "happens to work" from code that is deliberately designed.

> **Practice with Sythra:**  [Practice with AI tutor](https://app.sythra.ai/pricing)

## FAQ

### What is a tuple in Python?

A tuple is an immutable, ordered sequence of values, created with commas, e.g. t = (1, 2, 3). Once created, its elements cannot be changed, unlike a list.

### How do you create a one-element tuple in Python?

You need a trailing comma: t = ('a',). Without the comma, ('a') is just the string 'a' in parentheses, not a tuple.

### How do you swap two variables in Python without a temporary variable?

Use tuple assignment: a, b = b, a. Python evaluates the right side fully before assigning, so both values swap in a single line.

### How does a Python function return multiple values?

A function returns a single tuple containing multiple values, e.g. return min(t), max(t). The caller unpacks it with tuple assignment, e.g. lo, hi = min_max(t).

### What is the difference between *args gathering and scattering in Python?

Gathering happens in a function definition, e.g. def f(*args), collecting extra arguments into a tuple. Scattering happens at a function call, e.g. f(*t), spreading a tuple back out into separate arguments.

### Can a tuple be used as a dictionary key in Python?

Yes. Tuples are immutable and therefore hashable, so they can be dictionary keys, e.g. directory['Cleese', 'John'] = '...'. Lists cannot be keys because they are mutable and unhashable.

## Related

- [Lists in Python](https://app.sythra.ai/learn/python/lists-in-python) — The mutable sequence type tuples are most often compared against.
- [Dictionaries in Python](https://app.sythra.ai/learn/python/dictionaries-in-python) — Why tuples (not lists) can be used as dictionary keys.
- [Strings in Python](https://app.sythra.ai/learn/python/strings-in-python) — Another immutable sequence type, compared to tuples.
- [Data Types in Python](https://app.sythra.ai/learn/python/data-types) — Where tuple fits among Python's immutable, non-primitive types.
- [Sets in Python](https://app.sythra.ai/learn/python/sets-in-python) — Uniqueness, fast membership checks, and set math.
- [Python course hub](https://app.sythra.ai/learn/python) — All free Python explainers and the path into Agentic practice.

---

Written by Sythra — Learn machine learning by building. Practice this topic with Sythra's AI tutor: https://app.sythra.ai/pricing
