Skip to content

Lists

In Python, a List is an ordered, mutable collection of items. It is arguably the most fundamental and versatile data structure in the language. If you need to store a sequence of objects that might change over time, the list is your primary tool.

A list is a container that can hold any number of items, and those items can be of any data type (integers, strings, even other lists).

  • Ordered: The items have a defined order that will not change unless you explicitly change it.
  • Mutable: You can add, remove, or change items after the list has been created.
  • Dynamic: Lists can grow and shrink in size as needed.
creating_lists.py
# An empty list
empty_list = []
# A list of integers
prime_numbers = [2, 3, 5, 7, 11]
# A list with mixed types (though usually avoided for clarity)
mixed_list = [1, "Hello", 3.14, True]
# A nested list (list of lists)
matrix = [[1, 2], [3, 4]]

Since lists are ordered, every item has a specific position, known as its index.

Python uses zero-based indexing, meaning the first item is at index 0.

indexing.py
fruits = ["apple", "banana", "cherry", "date"]
print(fruits[0]) # Output: apple
print(fruits[2]) # Output: cherry
# Negative indexing starts from the end
print(fruits[-1]) # Output: date (last item)
print(fruits[-2]) # Output: cherry (second to last)

Slicing allows you to extract a sub-portion of a list. The syntax is list[start:stop:step].

  • start: The index where the slice begins (inclusive).
  • stop: The index where the slice ends (exclusive).
  • step: The interval between items (optional, defaults to 1).
slicing.py
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:5]) # [2, 3, 4]
print(numbers[:3]) # [0, 1, 2] (from start to index 3)
print(numbers[7:]) # [7, 8, 9] (from index 7 to end)
print(numbers[::2]) # [0, 2, 4, 6, 8] (every second item)
print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] (reversed)

Unlike strings, lists are mutable. You can change an item by assigning a new value to its index.

mutability.py
colors = ["red", "green", "blue"]
colors[1] = "yellow"
print(colors) # ['red', 'yellow', 'blue']

Python provides several built-in methods to manipulate lists:

MethodDescriptionExample
append(x)Adds an item x to the end.L.append(4)
extend(iterable)Appends all items from an iterable.L.extend([5, 6])
insert(i, x)Inserts item x at index i.L.insert(0, "first")
remove(x)Removes the first occurrence of x.L.remove("apple")
pop([i])Removes and returns item at index i (default last).item = L.pop()
clear()Removes all items from the list.L.clear()
index(x)Returns the index of the first occurrence of x.i = L.index("banana")
count(x)Returns the number of times x appears.num = L.count(1)
sort()Sorts the list in place.L.sort()
reverse()Reverses the list in place.L.reverse()

Lists should be your “go-to” structure whenever you have a collection of similar items that you need to maintain in a specific order.

Use a List when:

  1. You need a sequence of items.
  2. You need to modify the collection (add/remove items).
  3. The order of items is important (e.g., a “To-Do” list or a list of user IDs).

Avoid a List when:

  1. You need to store unique items only (use a Set).
  2. You need to look up items by a specific label or key (use a Dictionary).
  3. You have a massive amount of numerical data and need high-performance mathematical operations (use NumPy arrays).

Let’s build a simple logic for a task manager that utilizes various list operations.

task_manager.py
tasks = ["Email Client", "Buy Milk", "Fix Bug"]
# 1. Add a new task to the end
tasks.append("Go to Gym")
# 2. Add an urgent task to the beginning
tasks.insert(0, "URGENT: Server Down")
# 3. Mark a task as done (remove it)
tasks.remove("Buy Milk")
# 4. Remove the last task added
finished_task = tasks.pop()
# 5. Sort the remaining tasks alphabetically
tasks.sort()
print(f"Tasks remaining: {tasks}")
print(f"Just finished: {finished_task}")

Output:

Tasks remaining: ['Email Client', 'Fix Bug', 'URGENT: Server Down']
Just finished: Go to Gym

How does Python manage lists in memory? Python lists are implemented as Dynamic Arrays.

When you create a list, Python allocates a block of memory to hold a certain number of references to objects.

  1. Contiguous Memory: The references (pointers) are stored next to each other in memory. This is why indexing (L[5]) is extremely fast—the computer just calculates the memory offset.
  2. Over-allocation: To make append() efficient, Python doesn’t just allocate space for the items you have. It allocates extra space.
  3. Resizing: When the list is full and you try to append() another item, Python:
    • Allocates a new, larger block of memory.
    • Copies the existing references to the new block.
    • Deletes the old block.
    • Adds the new item.
  • Access by Index: $O(1)$ (Constant time)
  • Append: $O(1)$ (Amortized constant time)
  • Insert/Delete at the end: $O(1)$
  • Insert/Delete in the middle: $O(n)$ (Because all subsequent items must be shifted)
  • Search for an item: $O(n)$ (Because it might have to check every item)