Skip to main content
Python•Last Updated : 20 Oct 2026
~50 min read

Lists and Tuples in Python

Ordered collections in Python: creating and changing lists, searching and sorting, aliasing and copying, list comprehensions, nested lists, and immutable tuples with unpacking.

Lists: ordered, changeable collections

A list holds any number of values in order, written between square brackets and separated by commas. It is Python's general-purpose collection: a shopping list, the scores of a game, the lines of a file.

Lists share the sequence operations you met with strings: len(), indexing from 0, negative indexes, slicing and the in operator. The big difference is that lists are mutable: you can change, add and remove items after the list is created.

A list can hold values of different types, but in practice a list usually holds items of one kind, which is what makes looping over it simple.

PythonCreating and reading a list
1scores = [72, 95, 88, 61]
2print(scores)
3print(len(scores), scores[0], scores[-1])
4print(scores[1:3])
5print(95 in scores, 100 in scores)
6empty = []
7print(len(empty), empty)
Output
[72, 95, 88, 61]
4 72 61
[95, 88]
True False
0 []

Changing a list

Because lists are mutable, their methods usually change the list in place and return None:

  • append(x) adds one item at the end; extend(items) adds every item of another iterable.
  • insert(i, x) puts x before position i.
  • remove(x) deletes the first item equal to x (and raises ValueError if there is none).
  • pop() removes and returns the last item; pop(i) does the same for position i.
  • items[i] = x replaces one item, and del items[i] deletes one.

Adding at the end (append, pop()) is fast. Inserting or removing near the front moves every later item, which gets slow for very long lists.

Pythonappend, insert, remove, pop
1tasks = ["write tests", "fix bug"]
2tasks.append("deploy")
3tasks.insert(0, "read issue")
4print(tasks)
5tasks.remove("fix bug")
6last = tasks.pop()
7print(last, tasks)
8tasks[0] = "reread issue"
9tasks.extend(["review", "merge"])
10del tasks[1]
11print(tasks)
Output
['read issue', 'write tests', 'fix bug', 'deploy']
deploy ['read issue', 'write tests']
['reread issue', 'review', 'merge']

Searching and summarising

For questions about the contents of a list, reach for the built-ins first: in (is it there?), items.index(x) (where is the first one? raises ValueError if absent), items.count(x) (how many?), and sum, min, max for numbers.

in on a list checks the items one by one, so it gets slower as the list grows. When you test membership many times on a large collection, a set is much faster; the next lesson covers sets.

PythonAsking questions about a list
1rolls = [4, 6, 2, 6, 1, 6, 3]
2print(rolls.index(6), rolls.count(6))
3print(sum(rolls), min(rolls), max(rolls))
4print(f"Average roll: {sum(rolls) / len(rolls):.2f}")
5if 5 not in rolls:
6 print("No five was rolled")
Output
1 3
28 1 6
Average roll: 4.00
No five was rolled

Sorting

There are two ways to sort:

  • items.sort() sorts the list in place and returns None.
  • sorted(iterable) returns a new sorted list and leaves the original unchanged. It works on any iterable, including strings and tuples.

Both accept reverse=True and a key function that says what to compare. key=str.lower sorts text without regard to case; key=lambda p: p[1] sorts pairs by their second item. Python's sort is stable: items that compare equal keep their original order, which lets you sort by several criteria in steps.

Caution

`numbers = numbers.sort()` sets numbers to None. Either call numbers.sort() on its own line or write numbers = sorted(numbers).

Pythonsort() versus sorted()
1numbers = [5, 2, 9, 1]
2ordered = sorted(numbers)
3print(numbers, ordered)
4numbers.sort(reverse=True)
5print(numbers)
6print(numbers.sort())
7
8people = [("Ada", 36), ("Linus", 21), ("Grace", 36), ("Tim", 30)]
9print(sorted(people, key=lambda p: p[1]))
10print(sorted(people, key=lambda p: (-p[1], p[0])))
Output
[5, 2, 9, 1] [1, 2, 5, 9]
[9, 5, 2, 1]
None
[('Linus', 21), ('Tim', 30), ('Ada', 36), ('Grace', 36)]
[('Ada', 36), ('Grace', 36), ('Tim', 30), ('Linus', 21)]

Two names, one list: aliasing and copying

Assignment never copies. After b = a, both names refer to the same list object, so a change made through b is visible through a. With immutable values (numbers, strings) this never mattered; with lists it matters a lot.

To get an independent list, make a copy: a[:], list(a) or a.copy(). These are shallow copies: a new outer list holding the same inner objects. For lists of lists, use copy.deepcopy when the inner lists must be independent too.

Step through the trace and watch which lists change.

PythonSeeing aliasing with is
1a = [1, 2]
2b = a
3c = a.copy()
4print(b is a, c is a, c == a)
5
6def add_bonus(scores):
7 scores.append(10)
8
9mine = [5]
10add_bonus(mine)
11print(mine)
Output
True False True
[5, 10]

Aliasing versus copying

Step 1 of 5
1a = [1, 2]
2b = a
3b.append(3)
4c = a[:]
5c.append(4)
StepabcState Evolution
1[1, 2]——a refers to a new list [1, 2].

List comprehensions

Building a new list from an existing one is so common that Python has a dedicated syntax, the list comprehension:

[expression for item in iterable if condition]

It reads almost like English: "the expression, for each item, keeping those where the condition holds". The if part is optional. A comprehension replaces the three-line pattern empty list, loop, append with one line, and it is usually faster too.

Comprehensions can contain several for clauses, which read in the same order as nested loops. Keep them short: if a comprehension needs more than one condition and two loops, a normal loop is easier to read.

PythonFrom loop to comprehension
1prices = [12.5, 3.0, 8.75, 20.0]
2
3with_tax = []
4for p in prices:
5 with_tax.append(round(p * 1.1, 2))
6print(with_tax)
7
8print([round(p * 1.1, 2) for p in prices])
9print([p for p in prices if p >= 10])
10print([w.upper() for w in "keep it short".split()])
11print([(x, y) for x in range(2) for y in "ab"])
Output
[13.75, 3.3, 9.62, 22.0]
[13.75, 3.3, 9.62, 22.0]
[12.5, 20.0]
['KEEP', 'IT', 'SHORT']
[(0, 'a'), (0, 'b'), (1, 'a'), (1, 'b')]

Lists of lists

A list can contain other lists, which is the simplest way to represent a table or a grid: grid[row][col]. Loop over the rows, then over the items in each row.

One trap catches almost everyone once: [[0] * 3] * 3 creates three references to the same row, so changing one cell changes a whole column. Build each row separately with a comprehension: [[0] * 3 for _ in range(3)].

zip(*grid) transposes a grid: it pairs up the first items of every row, then the second items, and so on.

PythonGrids, the shared-row trap, and transposing
1grid = [[1, 2, 3], [4, 5, 6]]
2print(grid[1][2])
3for row in grid:
4 print(sum(row), end=" ")
5print()
6
7wrong = [[0] * 3] * 3
8wrong[0][0] = 9
9print(wrong)
10right = [[0] * 3 for _ in range(3)]
11right[0][0] = 9
12print(right)
13
14print([list(col) for col in zip(*grid)])
Output
6
6 15 
[[9, 0, 0], [9, 0, 0], [9, 0, 0]]
[[9, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 4], [2, 5], [3, 6]]

Tuples: fixed sequences

A tuple is an ordered sequence like a list, written with parentheses: (3, 4). The difference is that a tuple is immutable: once created, its items cannot be replaced, added or removed. A tuple with one item needs a trailing comma: ("only",).

Use a tuple for a small, fixed group of values that belong together, where position has meaning: a coordinate (x, y), an RGB colour, a (name, score) record, or several values returned from a function. Because they are immutable, tuples can be used as dictionary keys and set members, which lists cannot.

Unpacking assigns the items of any sequence to several names at once. A starred name collects whatever is left over.

PythonTuples and unpacking
1point = (3, 4)
2x, y = point
3print(x, y, len(point))
4
5record = ("Ada", 1815, "London")
6name, year, city = record
7print(f"{name} was born in {city} in {year}")
8
9first, *rest = [10, 20, 30, 40]
10print(first, rest)
11
12single = ("only",)
13print(type(single).__name__, type(("only")).__name__)
14
15distances = {("A", "B"): 12, ("B", "C"): 7}
16print(distances[("A", "B")])
Output
3 4 2
Ada was born in London in 1815
10 [20, 30, 40]
tuple str
12

List or tuple?

Both are ordered sequences; choose by how the data behaves.

Best Practices (Do)
  • ✓Use a list for a collection of similar items that grows, shrinks or gets sorted.
  • ✓Use a tuple for a fixed record whose positions have meanings, such as (x, y).
  • ✓Use tuples when you need a sequence as a dictionary key.
  • ✓Return several values from a function as a tuple and unpack them.
Common Pitfalls (Don't)
  • ✕Rely on a function not changing a list you passed to it; pass a copy if it must stay intact.
  • ✕Build a grid with [[0] * n] * n.
  • ✕Use a list as a dictionary key; it raises TypeError: unhashable type.
Key Takeaways & Summary
  • •Lists are mutable and most list methods change the list in place and return None.
  • •sorted() returns a new list; .sort() sorts in place.
  • •Assignment creates an alias, not a copy; use .copy() or a slice to copy.
  • •Tuples are immutable sequences, ideal for fixed records and unpacking.

Check your understanding

Suggested Quiz
Question 1 of 5

After `a = [1, 2]`, `b = a`, `b.append(3)`, what is `a`?

Practice

Remove duplicates while keeping order, find the second largest value in one pass, summarise a grid with comprehensions, and implement NumPy-style matrix operations with plain lists.

[Problem]

List Operations: Remove Duplicates Preserving Order

Reveal Solution & Analysis
Model Solution
[Problem]

Second Largest Value

Reveal Solution & Analysis
Model Solution
[Problem]

List Comprehension Challenges

Reveal Solution & Analysis
Model Solution
[Problem]

NumPy-Style Array Operations

Reveal Solution & Analysis
Model Solution

Quick Syntax & Reference Sheet

Concept / RuleSyntax / Usage
Create / additems = [3, 1]; items.append(2)
Removeitems.remove(x); last = items.pop()
Sort in place / copyitems.sort(); new = sorted(items, key=len)
Copycopy = items.copy() # or items[:]
Comprehension[x * 2 for x in items if x > 0]
Grid[[0] * cols for _ in range(rows)]
Tuple and unpackingx, y = (3, 4); first, *rest = items

Common Pitfalls & Developer Mistakes

✕ Mistake 1:

items = items.append(x)

Why it happens: append returns None, so the list is lost.

Correction: Call items.append(x) on its own line.
✕ Mistake 2:

Copying with b = a

Why it happens: Both names share one list, so changes leak.

Correction: b = a.copy() (or copy.deepcopy for nested lists).
✕ Mistake 3:

grid = [[0] * 3] * 3

Why it happens: All rows are the same list object.

Correction: grid = [[0] * 3 for _ in range(3)]
✕ Mistake 4:

Removing items while looping over the list

Why it happens: Items shift left and some are skipped.

Correction: items = [x for x in items if keep(x)]

Frequently Asked Questions

Are Python lists the same as arrays?

They play the role of arrays in other languages and index in constant time, but they can grow and hold mixed types. For large numeric data, libraries such as NumPy provide compact typed arrays.

Why use a tuple when a list can do everything?

Immutability is a guarantee: nobody can change a tuple by accident, and only immutable values can be dictionary keys or set members. A tuple also signals "fixed record" to the reader.

Is a comprehension always better than a loop?

No. It is better for building a list from another iterable in one clear step. For side effects (printing, saving) or complex logic, use a normal loop.

Hands-On Practice Problems

Article Tags:#Python#Lists#Tuples#List Comprehensions#Sorting
Was this lesson clear and helpful?

Discussion (0)

Sign in to ask questions and reply.
Loading discussion…
Discuss in community