On this page
Lists and Tuples
Lists and Tuples
Python provides two primary ordered sequence types: lists and tuples. They are similar in many ways — both are ordered, both support indexing and slicing, and both can hold elements of any type. The crucial difference is mutability: lists can be changed after creation, while tuples cannot. Understanding when to use each is a sign of Pythonic thinking.
Lists
A list is an ordered, mutable collection. Create one with square brackets:
# Creating lists
empty: list[int] = []
numbers = [1, 2, 3, 4, 5]
mixed = [42, "hello", 3.14, True, None] # any types
nested = [[1, 2], [3, 4], [5, 6]] # list of lists
# list() constructor
from_range = list(range(1, 11)) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
from_string = list("Python") # ['P', 'y', 't', 'h', 'o', 'n']
from_tuple = list((1, 2, 3)) # [1, 2, 3]
print(numbers)
print(from_range)Indexing and Slicing
Lists use zero-based indexing. Negative indices count from the end:
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
# Positive indexing
print(fruits[0]) # apple
print(fruits[2]) # cherry
# Negative indexing (counts from the end)
print(fruits[-1]) # elderberry
print(fruits[-2]) # date
# Slicing: list[start:stop:step]
print(fruits[1:4]) # ['banana', 'cherry', 'date'] (stop is exclusive)
print(fruits[:3]) # ['apple', 'banana', 'cherry'] (from start)
print(fruits[2:]) # ['cherry', 'date', 'elderberry'] (to end)
print(fruits[::2]) # ['apple', 'cherry', 'elderberry'] (every 2nd)
print(fruits[::-1]) # reversed list
# Slicing returns a new list — the original is unchanged
first_three = fruits[:3]
print(first_three) # ['apple', 'banana', 'cherry']
print(fruits) # ['apple', 'banana', 'cherry', 'date', 'elderberry']Modifying Lists
tasks = ["write tests", "fix bug", "deploy"]
# Change an element
tasks[1] = "fix critical bug"
print(tasks) # ['write tests', 'fix critical bug', 'deploy']
# Append a single element at the end
tasks.append("write docs")
# Insert at a specific index
tasks.insert(1, "code review")
# Extend with another iterable
tasks.extend(["update changelog", "notify team"])
print(tasks)
# Remove by value (removes first occurrence)
tasks.remove("code review")
# Remove by index and return the element
last = tasks.pop() # removes and returns the last element
second = tasks.pop(1) # removes and returns element at index 1
print(f"Removed: {last!r}, {second!r}")
# Delete by index or slice
del tasks[0]
# Clear all elements
backup = tasks.copy() # make a copy first
backup.clear()
print(backup) # []Useful List Methods
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print(numbers.count(5)) # 3 — how many times 5 appears
print(numbers.index(9)) # 5 — first index where 9 is found
numbers.sort() # sorts in place (ascending by default)
print(numbers) # [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
numbers.sort(reverse=True) # sorts descending
print(numbers) # [9, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1]
numbers.reverse() # reverses in place
print(numbers) # [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
# sorted() returns a NEW sorted list (does not modify original)
words = ["banana", "apple", "cherry", "date"]
sorted_words = sorted(words) # alphabetical
print(sorted_words) # ['apple', 'banana', 'cherry', 'date']
print(words) # original unchanged
by_length = sorted(words, key=len) # sort by word length
print(by_length) # ['date', 'apple', 'banana', 'cherry']tip type: info title: "sort() vs sorted()"
list.sort()modifies the list in place and returnsNone.sorted(iterable)returns a new sorted list and works on any iterable (not just lists). Usesort()when you want to modify the original; usesorted()when you need to preserve the original or sort something other than a list.
Tuples
A tuple is an ordered, immutable collection. Create one with parentheses (or just commas):
# Creating tuples
empty: tuple[()] = ()
single = (42,) # IMPORTANT: the comma makes it a tuple, not just parentheses
coordinates = (3, 7)
rgb = (255, 128, 0)
mixed = ("Alice", 30, True)
# Parentheses are optional
point = 10, 20
print(type(point)) # <class 'tuple'>
# tuple() constructor
from_list = tuple([1, 2, 3])
from_range = tuple(range(5))
print(coordinates)
print(rgb)Because tuples are immutable, they have only two methods: count() and index(). Indexing and slicing work exactly the same as with lists.
t = (10, 20, 30, 20, 50)
print(t.count(20)) # 2
print(t.index(30)) # 2
print(t[1:4]) # (20, 30, 20)
print(t[-1]) # 50
# Attempting to modify a tuple raises TypeError
try:
t[0] = 99
except TypeError as e:
print(f"Error: {e}") # 'tuple' object does not support item assignmentWhen to Use Tuples
Use tuples when the data should not change — they communicate intent:
# Configuration constants that should not change
DATABASE_CONFIG = ("localhost", 5432, "mydb")
HTTP_METHODS = ("GET", "POST", "PUT", "DELETE", "PATCH")
# Returning multiple values from a function (the implicit tuple)
def min_max(numbers: list[float]) -> tuple[float, float]:
return min(numbers), max(numbers)
lo, hi = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print(f"Min: {lo}, Max: {hi}") # Min: 1, Max: 9
# Tuples as dictionary keys (lists cannot be used as keys!)
location_map: dict[tuple[float, float], str] = {
(40.7128, -74.0060): "New York",
(51.5074, -0.1278): "London",
(-16.5000, -68.1500): "La Paz",
}
coord = (40.7128, -74.0060)
print(location_map[coord]) # New YorkUnpacking
Both lists and tuples support unpacking — assigning multiple variables from a sequence in one statement:
# Basic unpacking
first, second, third = [10, 20, 30]
print(first, second, third) # 10 20 30
x, y = (3, 7)
print(x, y) # 3 7
# Extended unpacking with *
head, *tail = [1, 2, 3, 4, 5]
print(head) # 1
print(tail) # [2, 3, 4, 5]
*init, last = [1, 2, 3, 4, 5]
print(init) # [1, 2, 3, 4]
print(last) # 5
first, *middle, last = "Python"
print(first) # P
print(middle) # ['y', 't', 'h', 'o']
print(last) # n
# Swapping variables without a temporary variable
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1
# Unpacking in loops
pairs = [(1, "one"), (2, "two"), (3, "three")]
for number, word in pairs:
print(f"{number} = {word}")
# Nested unpacking
matrix = [(1, 2, 3), (4, 5, 6)]
for a, b, c in matrix:
print(a, b, c)`enumerate()` — Index + Value
enumerate() is the Pythonic way to get both the index and value when iterating:
fruits = ["apple", "banana", "cherry", "date"]
# Non-Pythonic (C-style)
for i in range(len(fruits)):
print(i, fruits[i])
# Pythonic!
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# 0: apple
# 1: banana
# 2: cherry
# 3: date
# Custom start index
for rank, fruit in enumerate(fruits, start=1):
print(f"#{rank} {fruit}")
# #1 apple
# #2 banana
# ...
# Building an index dictionary
index = {fruit: i for i, fruit in enumerate(fruits)}
print(index) # {'apple': 0, 'banana': 1, 'cherry': 2, 'date': 3}`zip()` — Parallel Iteration
zip() combines multiple iterables into a single iterator of tuples:
names = ["Alice", "Bob", "Carol", "Dave"]
ages = [25, 30, 22, 28]
cities = ["Berlin", "London", "Paris", "Tokyo"]
for name, age, city in zip(names, ages, cities):
print(f"{name}, {age}, from {city}")
# Alice, 25, from Berlin
# Bob, 30, from London
# Carol, 22, from Paris
# Dave, 28, from Tokyo
# zip stops at the shortest iterable by default
short = [1, 2]
long = [10, 20, 30, 40]
print(list(zip(short, long))) # [(1, 10), (2, 20)]
# zip_longest fills in a default value for missing items
from itertools import zip_longest
print(list(zip_longest(short, long, fillvalue=0)))
# [(1, 10), (2, 20), (0, 30), (0, 40)]
# Unzipping — convert columns back to rows
pairs = [(1, "a"), (2, "b"), (3, "c")]
numbers, letters = zip(*pairs)
print(numbers) # (1, 2, 3)
print(letters) # ('a', 'b', 'c')tip type: tip title: "Use zip() to merge related lists"
When you have two or more parallel lists (e.g., names and scores collected separately),
zip()is the cleanest way to iterate or combine them. To create a dictionary from two lists, usedict(zip(keys, values)).
Named Tuples
For tuples that represent structured records, namedtuple or NamedTuple adds field names:
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
z: float = 0.0 # default value
p = Point(1.0, 2.5)
print(p) # Point(x=1.0, y=2.5, z=0.0)
print(p.x) # 1.0
print(p[1]) # 2.5 — still supports index access
print(p._asdict()) # {'x': 1.0, 'y': 2.5, 'z': 0.0}
# Named tuples are immutable like regular tuples
try:
p.x = 10
except AttributeError as e:
print(f"Error: {e}")
# _replace() returns a new named tuple with changed fields
p2 = p._replace(z=5.0)
print(p2) # Point(x=1.0, y=2.5, z=5.0)nextSteps
- dictionaries-and-sets
Sign in to track your progress