← Home

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]

Resources