Python Lists & Comprehensions
A list (list) in Python is an ordered, mutable, heterogeneous collection of items enclosed in square brackets ([...]).
- Ordered: Elements maintain the exact sequence in which they were added.
- Mutable: You can add, replace, sort, or delete elements in place without creating a new object.
- Heterogeneous: A single list can contain mixed data types (integers, strings, floats, booleans, and other lists).
# 1. Creating empty and populated lists
empty_list = []
numbers = [10, 20, 30, 40, 50]
mixed_list = ["Balaji", 25, 99.5, True, ["Python", "JS"]]
# 2. Inspecting length and items
print("Numbers list:", numbers)
print("Total elements:", len(numbers))
print("Mixed list:", mixed_list)
In CPython, a list is implemented as a dynamic array of pointer references. When the list grows beyond its current capacity, CPython automatically reallocates a larger memory buffer with amortized $O(1)$ append time.
Python provides three primary methods to insert new data into a list:
list.append(x): Adds itemxto the very end of the list in fast $O(1)$ time.list.insert(index, x): Inserts itemxat a specific index, shifting existing items to the right ($O(N)$ time).list.extend(iterable): Unpacks and appends all items from another list or iterable to the end.
fruits = ["Apple", "Banana"]
print("Initial list:", fruits)
# 1. append() - Adds to the end
fruits.append("Mango")
print("After append('Mango'):", fruits)
# 2. insert() - Inserts at index 1
fruits.insert(1, "Orange")
print("After insert(1, 'Orange'):", fruits)
# 3. extend() - Merges multiple items from another list
more_fruits = ["Grapes", "Pineapple"]
fruits.extend(more_fruits)
print("After extend():", fruits)
If you call fruits.append(["Grapes", "Pineapple"]), it adds the whole list as a single nested sub-list element! Use extend() when you want to unpack and add individual elements.
Modify or remove items from a list using index reassignment or deletion methods:
list[index] = new_val: Replaces the value at specified index.list.remove(val): Searches and removes the first occurrence ofval(raisesValueErrorif missing).list.pop(index): Removes and returns the item atindex(default removes last item).del list[index]: Deletes item at index or a slice of items.list.clear(): Empties all items from the list.
tasks = ["Email client", "Write code", "Bug fix", "Deploy app"]
print("Initial tasks:", tasks)
# 1. Update task at index 0
tasks[0] = "Check Slack messages"
print("After update tasks[0]:", tasks)
# 2. remove() by value
tasks.remove("Bug fix")
print("After remove('Bug fix'):", tasks)
# 3. pop() removes and returns last item
completed_task = tasks.pop()
print(f"Popped task: '{completed_task}' | Remaining: {tasks}")
# 4. del keyword for specific index
del tasks[0]
print("After del tasks[0]:", tasks)
- Use
.remove(val)when you know the value. - Use
.pop(i)when you know the index and need the returned value. - Use
del list[start:stop]to delete a range of items.
Python provides two ways to sort collections using the highly optimized Timsort algorithm ($O(N \log N)$ time complexity):
list.sort(): Sorts the list in-place (modifies original list, returnsNone).sorted(iterable): Built-in function that returns a new sorted list without modifying the original!
scores = [45, 99, 12, 78, 63, 85]
# 1. sorted() returns a NEW sorted list:
ascending_scores = sorted(scores)
descending_scores = sorted(scores, reverse=True)
print("Original scores list:", scores)
print("New sorted list (ascending):", ascending_scores)
print("New sorted list (descending):", descending_scores)
# 2. .sort() mutates the list IN PLACE:
scores.sort()
print("\nOriginal scores after .sort():", scores)
Never write scores = scores.sort()! Because .sort() mutates in-place, it returns None, which will overwrite your variable with None.
When you write list_b = list_a, Python does NOT copy the list. It creates a reference alias pointing to the exact same memory address! Modifying list_b will unintentionally corrupt list_a.
To make an independent clone, create a shallow copy with .copy() or [:]:
# โ REFERENCE ALIAS BUG:
original = [1, 2, 3]
alias = original
alias.append(99)
print("Reference Alias Demo:")
print("Original:", original) # [1, 2, 3, 99] (CORRUPTED!)
print("Alias: ", alias)
# โ
INDEPENDENT SHALLOW COPY:
source = [10, 20, 30]
cloned = source.copy() # or source[:]
cloned.append(999)
print("\nIndependent Copy Demo:")
print("Source:", source) # [10, 20, 30] (Safe and untouched!)
print("Cloned:", cloned) # [10, 20, 30, 999]
If your list contains nested sub-lists (e.g. [[1, 2], [3, 4]]), use import copy; copy.deepcopy(matrix) to clone nested child objects recursively.
List comprehensions offer a concise, readable, and faster syntax to create new lists by transforming and filtering existing sequences in a single line.
# Traditional 5-line for-loop approach:
squares_traditional = []
for x in range(1, 11):
if x % 2 == 0:
squares_traditional.append(x ** 2)
# โ
Modern 1-line List Comprehension:
squares_comprehension = [x ** 2 for x in range(1, 11) if x % 2 == 0]
print("Traditional Loop Result:", squares_traditional)
print("Comprehension Result: ", squares_comprehension)
# Filtering names with length >= 5:
names = ["Alex", "Balaji", "Chloe", "David", "Elizabeth"]
long_names = [n.upper() for n in names if len(n) >= 6]
print("Uppercase Long Names (len >= 6):", long_names)
List comprehensions execute at C-speed in the Python Virtual Machine (PVM) without the overhead of repeated .append() attribute lookups.
Never delete items from a list while iterating over it (for x in lst: if x==2: lst.remove(x)). Deleting elements shifts remaining indices left, skipping elements! Instead, iterate over a copy: for x in lst.copy(): or use a list comprehension.
Given a list of numbers from 1 to 20, create a new list containing only the cubes (x**3) of odd numbers using a list comprehension.
numbers = list(range(1, 21))
# List comprehension: cubes of odd numbers
odd_cubes = [x ** 3 for x in numbers if x % 2 != 0]
print("Numbers 1-20:", numbers)
print("Cubes of Odd Numbers:", odd_cubes)
Q What is the time complexity of append() vs insert(0, x)?
append() is O(1) constant time because it places the item at the end. insert(0, x) is O(N) linear time because all existing N elements must be shifted in memory.
Q How do I remove all occurrences of an item from a list?
Use a list comprehension: cleaned = [x for x in my_list if x != target_val] or a while loop: while target in my_list: my_list.remove(target).
Q Can a list contain another list inside it?
Yes! Python lists can be nested to any depth, enabling 2D matrices (grid = [[1, 2], [3, 4]]), 3D tensors, and tree structures.