Search⌘ K
AI Features

Lists and Indexing

Explore how to use Python lists to organize related data, focusing on creating lists, accessing elements with zero-based and negative indexing, slicing subsets, and modifying lists in place. This lesson helps you manage data sequences efficiently for practical programming tasks.

Up to this point, we have worked primarily with individual values, such as a single number, a single string, or a single boolean. In practice, however, real-world data rarely exists in isolation. Instead, data commonly appears as collections of related values that must be processed together.

Without a grouping mechanism, managing such data quickly becomes impractical. For example, if we needed to store and analyze the daily temperatures for an entire month, we would have to create and manage separate variables for each day. This approach is error-prone and cumbersome to process programmatically. Performing operations such as calculating an average, finding a maximum value, or iterating over the data would require inefficient, repetitive code.

Python addresses this problem with Python lists, a powerful and flexible data structure that allows us to store, order, and dynamically modify sequences of data.

Creating lists

A list is an ordered sequence of elements enclosed in square brackets [], with items separated by commas. Python lists are distinctive for two main reasons:

  1. Dynamic sizing: They can grow or shrink as we add or remove items.

  2. Heterogeneity: Unlike strict arrays in other languages that require a single data type, a Python list can hold different types of data at the same time. We can store integers, strings, booleans, and even other lists inside a single container.

We define a list by assigning a sequence of values to a variable name. Learning how to create a list in Python is the first step toward managing complex datasets.

Python 3.14.0
# Defining lists with different types of data
server_ids = [101, 102, 103, 104]
mixed_config = ["production", 8080, True, 3.11]
empty_list = []
print(f"Servers: {server_ids}")
print(f"Configuration: {mixed_config}")
print(f"Empty: {empty_list}")
print(f"Empty List Type: {type(empty_list)}")
  • Line 2: We create a list, server_ids, containing four integers.

  • Line 3: We create a list, mixed_config, containing a string, an integer, a boolean, and a float, demonstrating Python's ability to mix types.

  • Line 4: We create an empty list, which is useful when we plan to populate data later.

  • Lines 6–8: We print the lists to the console.

  • Line 9: We verify the type of empty_list via the type() method, which is <class 'list'>. Try replacing empty_list with other declared lists.

Accessing elements by index

Lists preserve the order in which we add items. To retrieve a specific item, we use its index, which represents its position in the sequence. Python uses zero-based indexing, meaning the first item is at index 0, the second is at index 1, and so on.

Python 3.14.0
# Accessing elements using positive indexes
colors = ["red", "green", "blue", "yellow"]
primary = colors[0]
secondary = colors[2]
print(f"First color: {primary}")
print(f"Third color: {secondary}")
  • Line 2: We define a list with four string elements.

  • Line 4: We access the element at index 0, which is "red".

  • Line 5: We access the element at index 2, which is "blue" (the third item).

If we attempt to access an index that does not exist, Python raises an IndexError. Try accessing the colors[4] and notice the updated behavior.

Negative indexing

As discussed earlier, Python supports negative indexing, where an index of -1 refers to the last element, -2 refers to the second-to-last element, and so on. Look at the example below.

Python 3.14.0
# Accessing elements from the end of the list
file_paths = ["/bin", "/usr", "/etc", "/var", "/home"]
last_path = file_paths[-1]
second_last = file_paths[-2]
print(f"Last path: {last_path}")
print(f"Second to last: {second_last}")
  • Line 2: We define a list of five string paths.

  • Line 4: We access index -1, retrieving "home".

  • Line 5: We access index -2, retrieving "/var".

Mapping positive and negative indexes to the same list elements
Mapping positive and negative indexes to the same list elements

Slicing lists

Sometimes we need a subset of a list rather than a single item. By mastering how to slice a list in Python using the [start:stop:step] syntax, you can extract exactly the data you need.

  • start: The index where the slice begins (inclusive).

  • stop: The index where the slice ends (exclusive).

  • step: The interval between indexes (optional, defaults to 1).

If we omit start, Python defaults to 0. If we omit stop, Python defaults to the end of the list.

Python 3.14.0
# Extracting sub-lists using slicing
metrics = [10, 20, 30, 40, 50, 60, 70, 80]
first_three = metrics[0:3]
middle_segment = metrics[3:6]
every_second = metrics[::2]
reverse_metrics = metrics[::-1]
print(f"First three: {first_three}")
print(f"Middle segment: {middle_segment}")
print(f"Every second: {every_second}")
print(f"Reversed: {reverse_metrics}")
  • Line 4: We slice from index 0 up to (but not including) 3, resulting in [10, 20, 30].

  • Line 5: We slice from index 3 up to 6, resulting in [40, 50, 60].

  • Line 6: We omit start and stop, but provide a step of 2, taking every other item starting from the first.

  • Line 7: We use a step of -1 to create a copy of the list in reverse order.

Modifying lists (mutability)

Unlike strings, which are immutable (unchangeable), lists are mutable. We can change the value of an element directly by assigning a new value to its index. We can even replace entire slices of the list at once.

To prove that the list is modified in place, rather than destroyed and recreated, we can check its unique identity using the id() function. If the ID remains the same before and after the update, we know it is the same object.

Python
# Modifying list elements in place
user_roles = ["admin", "editor", "viewer", "guest"]
# Printing the original ID first
print(f"Original ID: {id(user_roles)}")
# Printing the original roles first
print(f"Original roles: {user_roles}")
# Update a slice (replace multiple elements)
user_roles[0:2] = ["superuser", "moderator"]
# Printing the ID after replacing the elements
print(f"Same ID: {id(user_roles)}")
# Printing the roles after replacing the elements
print(f"Updated roles: {user_roles}")
  • Line 4: We use the id() function to inspect the object's memory address. Think of this as the list's "digital fingerprint." By printing this before making changes, we establish a baseline to prove whether the object is replaced or simply updated.

  • Line 9: We perform a slice assignment. Unlike standard variable assignment (which would replace the entire list), this syntax targets a specific subsection of the existing list. It allows us to swap out multiple items ("admin" and "editor") for new values ("superuser" and "moderator") in a single operation.

  • Line 12: We print the id() again to prove the concept of in-place modification. Because the ID matches the one from line 4, we confirm that Python did not create a new list object; it simply updated the contents of the existing container. This persistence is the defining characteristic of mutable data types.

We have now unlocked the ability to manage collections of data. By mastering zero-based indexing, negative lookups, slicing, and in-place modification, we can efficiently handle sequences of data. These fundamental operations are the bedrock for the more advanced list methods and iteration techniques we will explore next.