Back to Blog
Python

Python List: Syntax, Operations, and Performance

python list: Learn how Python lists work, from basic operations to slicing, comprehensions, and performance tradeoffs in real applications.

listdata structuressequenceslicingcomprehensionperformance
Illustration of a Python list as an ordered sequence of elements with indexing and slicing operations.

Python's list is a mutable sequence type that stores elements in insertion order and allows fast access by index. It is one of the most commonly used data structures in Python, and understanding its behavior is essential for writing efficient code. This article covers the core syntax, common operations, slicing, comprehensions, and the performance tradeoffs that matter in real applications.

Core Behavior of Python Lists

A list is implemented as a dynamic array of references. Each element is a reference to an object, so a list can hold items of different types. The list itself keeps track of the array's size and capacity. When you append an element and the underlying array is full, Python allocates a larger array and copies the references over. This is why append has amortized O(1) time complexity, while operations that shift elements, such as insert at the beginning, are O(n).

Lists are mutable: you can change elements, add new ones, or remove existing ones. This mutability distinguishes them from tuples, which are immutable. If you need a fixed sequence that should not change, a tuple is a safer choice because it prevents accidental modification.

Creating Lists and Basic Operations

The simplest way to create a list is with square brackets:

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

You can also use the list() constructor to convert an iterable:

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

Common operations include append to add an element at the end, extend to add multiple elements, insert to place an element at a specific index, and pop to remove and return an element. Here is a quick example:

items = [1, 2, 3] items.append(4) # [1, 2, 3, 4] items.extend([5, 6]) # [1, 2, 3, 4, 5, 6] items.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6] last = items.pop() # returns 6, items is now [0, 1, 2, 3, 4, 5]

remove deletes the first occurrence of a value and raises ValueError if the value is not present. index returns the position of the first match. These methods modify the list in place and return None for operations that change the list, which is a common source of confusion when chaining.

Indexing and Slicing

Python lists support both positive and negative indexing. -1 refers to the last element, -2 to the second last, and so on. Slicing creates a new list that contains a subset of the original. The syntax is list[start:stop:step]. The start index is inclusive, stop is exclusive, and step controls the stride.

letters = ['a', 'b', 'c', 'd', 'e', 'f'] print(letters[1:4]) # ['b', 'c', 'd'] print(letters[::2]) # ['a', 'c', 'e'] print(letters[::-1]) # ['f', 'e', 'd', 'c', 'b', 'a']

Slicing always returns a new list, even if the slice covers the entire list. This is a shallow copy: the elements themselves are not copied, only the references. If you need a deep copy of the elements, you must copy each object individually.

List Methods and Mutability

Many list methods operate in place. sort orders the list according to a key function or natural ordering. reverse reverses the elements. count returns the number of occurrences of a value. These methods do not return a new list; they change the existing one.

nums = [3, 1, 2] nums.sort() # nums becomes [1, 2, 3] nums.reverse() # nums becomes [3, 2, 1] print(nums.count(2)) # 1

Because these methods return None, code like new_list = my_list.sort() will set new_list to None. This is a frequent mistake. If you need a sorted copy, use the built-in sorted() function, which returns a new list.

List Comprehensions for Concise Construction

List comprehensions provide a compact syntax for building lists from existing iterables. They are often more readable and slightly faster than a for loop with append. A basic comprehension has the form [expression for item in iterable]. You can add a condition with if.

squares = [x * x for x in range(10)] even_squares = [x * x for x in range(10) if x % 2 == 0]

Nested comprehensions are possible, though they can hurt readability. For example, flattening a matrix:

matrix = [[1, 2], [3, 4]] flat = [num for row in matrix for num in row] # [1, 2, 3, 4]

Use a comprehension when the logic is simple and fits on one or two lines. For complex transformations, a regular loop with comments is usually clearer.

Performance Considerations

Lists are optimized for appending and accessing elements by index. The amortized cost of append is O(1), and indexing is O(1). However, inserting or removing an element in the middle requires shifting all subsequent elements, making it O(n). If you frequently insert at the beginning, consider using collections.deque, which offers O(1) append and pop on both ends.

Memory usage is another factor. A list stores references to objects, not the objects themselves. Each reference is typically 8 bytes on a 64-bit system. The list also reserves extra capacity to avoid reallocating on every append. This overhead can be significant when storing millions of small integers. For numeric data, the array module or NumPy arrays are more memory-efficient because they store values directly.

When you need to check whether an item exists, item in list is O(n) because it scans the list. If membership tests are frequent, a set is the better choice. Lists are not the right tool for every collection problem; knowing when to switch to a tuple, set, or deque is part of writing efficient Python.

Common Pitfalls and How to Avoid Them

One classic pitfall is using a mutable default argument in a function definition. Because the default is evaluated only once, the same list is shared across all calls. Use None and create a new list inside the function instead.

def add_item(item, container=None): if container is None: container = [] container.append(item) return container

Another issue is modifying a list while iterating over it. Removing elements during iteration can skip items or raise RuntimeError. A safer approach is to iterate over a copy or build a new list with a comprehension.

# Removing all even numbers from a list nums = [1, 2, 3, 4, 5] nums = [n for n in nums if n % 2 != 0]

Finally, remember that slicing creates a shallow copy. If you have a list of lists and you copy it with copy_list = original[:], both lists share the same inner lists. Modifying an inner list affects both. Use copy.deepcopy if you need full independence.

Understanding these behaviors helps you avoid subtle bugs and choose the right data structure for the task.

python list: Practical Usage and Code Examples | RYUSLOG DEV