Python lists, explained
The list is Python's workhorse sequence, and interviewers use it to check whether you
understand slicing, in-place vs returning operations, and the cost of the operations you
reach for. This guide covers slicing mechanics, the append/extend/insert distinction,
and sort vs sorted.
Lists vs arrays
A Python list is a dynamic, heterogeneous sequence of object references — it can hold
mixed types and resize automatically:
mixed = [1, "two", 3.0, [4]] # perfectly legal
It is not a contiguous block of numbers like a C array. For large numeric data, use
array.array (typed) or, in practice, NumPy arrays, which store raw values compactly and
run far faster for math.
Slicing, including negative steps
Slicing uses [start:stop:step], where stop is exclusive and any part can be
omitted:
nums = [0, 1, 2, 3, 4, 5]
nums[1:4] # [1, 2, 3]
nums[:3] # [0, 1, 2]
nums[2:] # [2, 3, 4, 5]
nums[::2] # [0, 2, 4] — every other element
nums[-2:] # [4, 5] — last two
nums[::-1] # [5, 4, 3, 2, 1, 0] — reversed
A slice creates a shallow copy — nums[:] is a common way to copy a list. The copy is
shallow, so nested objects are shared, not duplicated.
Slice assignment
You can assign to a slice to replace, insert, or delete a whole region in place:
nums = [0, 1, 2, 3, 4]
nums[1:3] = ["a", "b", "c"] # [0, 'a', 'b', 'c', 3, 4] — replace with different length
nums[:] = [] # clears the list in place
append vs extend vs insert
These three add elements but differ in what and where:
lst = [1, 2]
lst.append([3, 4]) # [1, 2, [3, 4]] — adds ONE element (the list itself)
lst = [1, 2]
lst.extend([3, 4]) # [1, 2, 3, 4] — adds each element of the iterable
lst = [1, 2]
lst.insert(0, 99) # [99, 1, 2] — insert at an index (O(n): shifts elements)
append is O(1) amortised; insert(0, x) is O(n) because everything shifts. If you
frequently add to the front, use collections.deque instead.
sort vs sorted
list.sort() sorts in place and returns None; sorted() returns a new sorted
list and works on any iterable:
nums = [3, 1, 2]
nums.sort() # nums is now [1, 2, 3]; returns None
result = nums.sort() # BUG: result is None
squared = sorted([3, 1, 2]) # [1, 2, 3], original untouched
Both accept key and reverse, and both are stable (equal elements keep their order):
words = ["bb", "a", "ccc"]
sorted(words, key=len) # ['a', 'bb', 'ccc']
sorted(words, key=len, reverse=True)
Are comprehensions faster than loops?
Yes, modestly — a list comprehension is usually faster than the equivalent for loop with
.append(), because the iteration and appending happen in C rather than via repeated
attribute lookups and method calls:
squares = [x * x for x in range(1000)] # faster + clearer than a loop
Use a comprehension when you're building a list; use a plain loop when you're doing it for side effects.
Recap
A list is a resizable, heterogeneous sequence of references — reach for NumPy when you need
fast numeric arrays. Slicing [start:stop:step] is exclusive of stop, supports negative
steps ([::-1] reverses), and returns a shallow copy. append adds a single item,
extend adds each item of an iterable, and insert is O(n). Use list.sort() for an
in-place sort (returns None) and sorted() to get a new list; both are stable and take a
key. Prefer comprehensions when building lists.