059. List Slicing
Extract subsequences without mutation
059. List Slicing
Aryan’s monitor keeps a rolling history of RAM readings. He needs to extract windows — the last 60 seconds, the last 10 readings, every other sample for a downsampled chart:
history = [...] # list of (timestamp, used_pct) tuples, newest last
# Last 60 readings (one per second)
last_minute = history[-60:] # negative slice from end
# Most recent 10 for the summary panel
recent = history[-10:]
# Downsample: every other reading for a sparkline
sparkline = history[::2]
# First 5 readings from startup (baseline)
baseline = history[:5]
# Drop the first and last (warm-up / cool-down)
trimmed = history[1:-1]The key insight: slices never raise IndexError. history[-100:] on a 10-element list just returns all 10. This makes slicing safe for “give me the last N” even when the list hasn’t accumulated N readings yet.
💡 Fun fact: Python’s slice notation [start:stop:step] was directly adopted by NumPy for multi-dimensional array slicing: matrix[1:3, 0:2] extracts rows 1-2 and columns 0-1. This consistent syntax is one reason Python became the dominant language for data science — the same mental model for slicing lists works on arrays, DataFrames, and tensors in libraries like pandas and PyTorch.
⚠️ Watch out: Slices always return a new list (a shallow copy), so modifying the slice doesn’t affect the original. But the elements inside the slice are the same objects — if they’re mutable (like dicts), mutating them through the slice does affect the original list. This shallow-copy behavior surprises beginners working with lists of dicts.
🤔 Think about it: lst[::-1] reverses a list by creating a new list with step -1. Python also has lst.reverse() which reverses in-place and returns None. If you need a reversed copy for display while keeping the original intact, which do you use? And when would in-place reversal with .reverse() be preferable?
Learning objectives
- Extract sublists with lst[start:stop]
- Use [:n] and [-n:] for first/last n items
- Use [::step] for stride-based slicing
Key concepts
- list slicing
- start stop step
- sublist
Try it
Concept detail
List slicing creates a new list — a copy of the specified range. Original is not modified. lst[start:stop] — stop is exclusive: lst[1:4] gives elements at 1, 2, 3 lst[:n] — first n items lst[-n:] — last n items lst[::2] — every other element (step 2) lst[::-1] — reversed copy lst[1:-1] — everything except first and last
Slices never raise IndexError: [1, 2, 3][0:100] == [1, 2, 3] — truncates at end of list [1, 2, 3][-100:] == [1, 2, 3] — truncates at start
Slicing is O(k) where k is the slice length — it copies the elements. Slice assignment: lst[1:3] = [10, 20] replaces in-place (can change length).
Common pattern in monitors and pagination: history[-n:] # last n readings items[page*size:(page+1)*size] # page of results
Solution
def get_page(items, page, page_size):
start = page * page_size
end = start + page_size
return items[start:end]
def first_n(items, n):
return items[:n]
def last_n(items, n):
return items[-n:]
def every_other(items):
return items[::2]Tests
ITEMS = list(range(20)) # [0, 1, 2, ..., 19]
def test_first_page():
assert get_page(ITEMS, 0, 5) == [0, 1, 2, 3, 4]
def test_second_page():
assert get_page(ITEMS, 1, 5) == [5, 6, 7, 8, 9]
def test_third_page():
assert get_page(ITEMS, 2, 5) == [10, 11, 12, 13, 14]
def test_first_n():
assert first_n([10, 20, 30, 40], 3) == [10, 20, 30]
def test_first_n_more_than_length():
assert first_n([1, 2], 10) == [1, 2] # no IndexError
def test_last_n():
assert last_n([10, 20, 30, 40], 2) == [30, 40]
def test_every_other():
assert every_other([1, 2, 3, 4, 5, 6]) == [1, 3, 5]