pyguides

Lists, Tuples, and Dictionaries

Python gives you several powerful ways to organize and store data. Lists, tuples, and dictionaries are the three most important collection types you’ll use in virtually every program you write. Understanding how each one works and knowing when to reach for which will make your code cleaner, faster, and easier to maintain. This tutorial walks through lists and tuples side by side so you can see how each data structure handles creation, access, and iteration, and where dictionaries fit as a third distinct tool for key-value lookups.

Lists: your go-to ordered collection

Lists are the workhorse of Python data structures. They’re ordered, mutable collections that can hold items of any type. Think of a list as a shopping list: you can add items, remove them, and change the order.

Creating Lists

Creating a list is straightforward. Use square brackets and separate items with commas:

# A list of fruits
fruits = ["apple", "banana", "cherry"]

# A list of mixed types
mixed = [1, "hello", 3.14, True]

# An empty list
empty = []

# Using the list() constructor
numbers = list(range(5))  # [0, 1, 2, 3, 4]

Accessing list items

Each item in a list has an index, starting from zero. Use square brackets to access individual items. Python supports negative indexing as well, where minus one refers to the last element and minus two refers to the second-to-last, which is a convenient shorthand when you want the tail end of a collection without computing its length.

fruits = ["apple", "banana", "cherry", "date"]

print(fruits[0])   # "apple" (first item)
print(fruits[1])   # "banana" (second item)
print(fruits[-1])  # "date" (last item)
print(fruits[-2])  # "cherry" (second-to-last)

Accessing individual items by index is useful when you know the exact position, but real programs more often need to work with ranges of elements. Python’s slice notation lets you extract contiguous subsequences in a single expression, using a compact start:stop:step syntax that works consistently across lists, tuples, and strings.

You can also slice lists to get multiple items at once:

fruits = ["apple", "banana", "cherry", "date", "elderberry"]

print(fruits[1:4])    # ["banana", "cherry", "date"]
print(fruits[:3])     # ["apple", "banana", "cherry"]
print(fruits[2:])     # ["cherry", "date", "elderberry"]
print(fruits[::2])    # ["apple", "cherry", "elderberry"] (every second item)

Now that you have seen how to create lists and extract subsets of their contents, the next step is modifying them in place. Lists are mutable, meaning you can change them after creation, and this mutability is what makes lists the go-to choice for most everyday programming tasks where data changes over time.

Modifying Lists

Lists are mutable, meaning you can change them after creation:

fruits = ["apple", "banana", "cherry"]

# Change an item
fruits[0] = "avocado"
print(fruits)  # ["avocado", "banana", "cherry"]

# Add items
fruits.append("date")           # Add to end
fruits.insert(1, "apricot")     # Insert at index 1
fruits.extend(["elderberry", "fig"])  # Add multiple items
print(fruits)  # ["avocado", "apricot", "banana", "cherry", "date", "elderberry", "fig"]

# Remove items
fruits.pop()             # Remove and return last item
fruits.remove("banana")  # Remove first occurrence
del fruits[0]           # Delete item at index
print(fruits)  # ["apricot", "cherry", "date", "elderberry"]

Once you have populated a list, you will frequently need to ask questions about its contents or produce aggregate values from it. Python includes several built-in functions that operate on any iterable, not just lists, and they are implemented in the interpreter’s C layer for performance that hand-written loops rarely match.

Useful list operations

Python provides many built-in functions for working with lists:

numbers = [3, 1, 4, 1, 5, 9, 2, 6]

print(len(numbers))      # 8 (length)
print(sum(numbers))      # 31 (sum of numbers)
print(max(numbers))      # 9 (maximum)
print(min(numbers))      # 1 (minimum)
print(sorted(numbers))  # [1, 1, 2, 3, 4, 5, 6, 9] (new sorted list)

# Check membership
print(5 in numbers)     # True
print(7 in numbers)     # False

Iterating over lists

Built-in functions like len, sum, min, and max are helpful for quick answers about a list’s contents, but most real-world data processing involves stepping through items one at a time to apply conditional logic or build transformed results. You will often need to loop through list items. Python gives you several patterns for walking through a list, and the right choice depends on whether you need just the values, the indices, or both simultaneously.

fruits = ["apple", "banana", "cherry"]

# Simple iteration
for fruit in fruits:
    print(fruit)

# With index using enumerate()
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")

# Using list comprehension
squares = [x**2 for x in range(5)]
print(squares)  # [0, 1, 4, 9, 16]

Tuples: immutable ordered collections

Tuples are like lists, but with one key difference: they are immutable. Once you create a tuple, you cannot change it. Use tuples when you want to ensure data is not accidentally modified, which is especially important when passing data between functions or using collections as dictionary keys where hash stability is required, since mutable objects like lists cannot serve as dictionary keys at all. This immutability guarantee makes tuples the safe default for data that travels between system boundaries and should not be accidentally modified along the way.

Creating Tuples

Use parentheses (or just commas) to create tuples:

# Using parentheses
point = (10, 20)
rgb = (255, 128, 0)

# Without parentheses (tuple packing)
coordinates = 10, 20, 30

# Single item tuple (note the comma)
single = ("hello",)  # Without comma, it's just a string

# Empty tuple
empty = ()

# Using tuple() constructor
text = tuple("hello")  # ('h', 'e', 'l', 'l', 'o')

Once you have created a tuple, you access its elements the same way you do with a list: using square brackets and zero-based indices. The fact that tuples share the same indexing and slicing interface as lists is intentional and makes switching between the two collection types straightforward as your requirements change.

Accessing tuple items

Tuple indexing and slicing work exactly like lists:

point = (10, 20, 30)

print(point[0])   # 10
print(point[-1])  # 30
print(point[1:3]) # (20, 30)

Tuple Unpacking

One of the most powerful features of tuples is unpacking: assigning elements to multiple variables at once. This single feature eliminates entire categories of bugs that arise from temporary variables and manual index arithmetic. Unpacking also makes Python’s multiple return values feel natural rather than like a workaround.

# Basic unpacking
point = (10, 20, 30)
x, y, z = point
print(x, y, z)  # 10 20 30

# Swap values without temporary variable
a, b = 1, 2
a, b = b, a
print(a, b)  # 2 1

# Extended unpacking (Python 3.x)
first, *middle, last = [1, 2, 3, 4, 5]
print(first)   # 1
print(middle)  # [2, 3, 4]
print(last)    # 5

# Use _ for unused values
rgb = (255, 128, 0)
red, green, _ = rgb

When to use tuples

Tuples are perfect for:

  • Returning multiple values from functions
  • Dictionary keys (lists cannot be keys because they’re mutable)
  • Fixed collections that shouldn’t change
  • Named constants or configuration values

The real power of tuples comes from combining their immutability with unpacking. When a function returns a tuple, the caller can immediately destructure the result into named variables without worrying about the function accidentally modifying shared state. This gives you the safety of immutable data with the convenience of multiple return values. The example below shows a function that computes three summary statistics at once and returns them as a tuple that the caller unpacks in a single line.

# Function returning multiple values
def get_stats(numbers):
    return min(numbers), max(numbers), sum(numbers)

minimum, maximum, total = get_stats([1, 2, 3, 4, 5])
print(f"Min: {minimum}, Max: {maximum}, Total: {total}")

Dictionaries: key-value pairs

Dictionaries store data as key-value pairs. They provide fast lookup by key, much faster than searching through a list, because Python implements dictionaries as hash tables with constant-time average lookup regardless of how many entries they contain. Think of a dictionary like a real-world dictionary: you look up a word to find its definition, and the search takes the same effort whether the book has a thousand pages or a million. Unlike lists and tuples, which use sequential integer positions, dictionaries let you associate meaningful names with values, so your code reads closer to the domain it models. A shopping cart stored as a dictionary can use product IDs as keys and quantities as values, making it immediately clear what each entry represents without counting positions. Dictionaries also enforce key uniqueness, so the same key always points to a single value no matter how many times you update it.

Creating Dictionaries

Use curly braces with key-value pairs:

# Basic dictionary
person = {
    "name": "Alice",
    "age": 30,
    "city": "London"
}

# Using dict() constructor
person = dict(name="Alice", age=30, city="London")

# Empty dictionary
empty = {}

Once you have a dictionary, the primary operation you will perform is looking up values by their keys. Python gives you two different ways to do this, and the choice between them depends on how you want to handle missing keys.

Accessing dictionary values

Access values using square brackets with the key, or the .get() method:

person = {"name": "Alice", "age": 30, "city": "London"}

# Using brackets (raises KeyError if key doesn't exist)
print(person["name"])  # "Alice"

# Using .get() (returns None if key doesn't exist)
print(person.get("name"))     # "Alice"
print(person.get("country"))  # None

# Provide default value
print(person.get("country", "Unknown"))  # "Unknown"

Modifying dictionaries

Dictionaries are mutable: you can add, change, and remove entries at any time without recreating the entire structure. This is a fundamental difference from tuples, where every modification requires building a new object. The flexibility of in-place mutation makes dictionaries ideal for accumulating data, caching results, or gradually building up a configuration object from multiple sources. The methods shown below cover the four most common mutation patterns you will reach for in everyday code.

person = {"name": "Alice", "age": 30}

# Add new key-value pair
person["city"] = "London"
print(person)  # {"name": "Alice", "age": 30, "city": "London"}

# Update with another dictionary
person.update({"age": 31, "country": "UK"})
print(person)  # {"name": "Alice", "age": 31, "city": "London", "country": "UK"}

# Remove entries
del person["country"]           # Delete specific key
age = person.pop("age")         # Remove and return value
print(person)  # {"name": "Alice", "city": "London"}

# Clear all entries
person.clear()
print(person)  # {}

Once you have populated a dictionary, you will often need to walk through its contents to display data, search for specific entries, or transform the stored values. Python provides three distinct view objects for this purpose, each giving you a different perspective on the same underlying data without creating unnecessary copies. Views stay connected to the dictionary they came from, so if you add or remove keys after creating a view, the view automatically reflects those changes. This dynamic behavior is especially useful in loops that modify the dictionary mid-iteration, though you should be careful about adding new keys while iterating since that can trigger a runtime error. When you need a stable snapshot instead, just cast the view to a list with the list constructor.

Dictionary views and iteration

You can iterate over keys, values, or both:

person = {"name": "Alice", "age": 30, "city": "London"}

# Iterate over keys
for key in person:
    print(key)

# Iterate over values
for value in person.values():
    print(value)

# Iterate over key-value pairs
for key, value in person.items():
    print(f"{key}: {value}")

# Check if key exists
print("name" in person)    # True
print("country" in person) # False

Useful dictionary operations

The keys, values, and items methods each return dynamic view objects that reflect live changes to the dictionary. Converting them to a list with the list constructor gives you a static snapshot you can store or pass to other functions without worrying about the underlying dictionary being modified during iteration. The merge operator introduced in Python 3.9 provides a clean syntax for combining two dictionaries without mutating either original, and dictionary comprehensions offer a concise way to build dictionaries from iterables in a single expression.

# Get all keys, values, or items
person = {"name": "Alice", "age": 30, "city": "London"}
print(list(person.keys()))   # ["name", "age", "city"]
print(list(person.values())) # ["Alice", 30, "London"]
print(list(person.items()))  # [("name", "Alice"), ("age", 30), ("city", "London")]

# Dictionary comprehension
squares = {x: x**2 for x in range(5)}
print(squares)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Merge dictionaries (Python 3.9+)
a = {"x": 1}
b = {"y": 2}
merged = a | b
print(merged)  # {"x": 1, "y": 2}

Choosing the right data structure

Now you understand all three. But when should you use each?

Use CaseData StructureWhy
Ordered collection that changesListMutable, supports append/pop/insert
Fixed ordered dataTupleImmutable, slightly faster, hashable
Fast lookup by keyDictionaryO(1) lookup time vs O(n) for lists
Collection of unique itemsSet (not covered here)Automatic duplicate handling
Key-value associationsDictionaryDesigned for this use case

Quick decision guide

  • Need to modify the collection? Use a list.
  • Need the collection as a dictionary key or in a set? Use a tuple.
  • Need to look up items by name/ID? Use a dictionary.
  • Need to ensure data isn’t accidentally changed? Use a tuple.
  • Need to store multiple values and iterate in order? Use a list.

Next Steps

Now that you understand these fundamental data structures, you’re ready to move on to the next tutorial in the series. In the next lesson, you’ll learn about modules and imports: how to organize your code across multiple files and use the vast Python standard library.

Continue your learning journey with: Modules and Imports

See also