skip to content

What can slice assignment lst[1:3] = [...] do on a Python list that lst[1] = x cannot?

level: middleimportance: should knowfreq 45%

answer

  1. One replaces an element, one replaces a region
  2. The length can change
  3. Insertion is an empty target region
  4. The right-hand side is any iterable
  5. A step makes the counts strict

basics

~10 s

Slice assignment replaces a whole region with the items of any iterable, so it can grow or shrink the list. Single-index assignment only swaps one element and never changes the length.

solid answer

~50 s

`lst[1] = x` replaces exactly one element. `lst[1:3] = iterable` replaces the whole region, splicing in however many items the iterable yields, so the list can grow, shrink or stay the same length. That makes `lst[2:2] = [a, b]` an insertion and `lst[1:3] = []` (or `del lst[1:3]`) a deletion. Two traps follow. The right-hand side must be an *iterable* and is consumed item by item, so `lst[0:1] = "hi"` splices in two characters rather than one string. And an **extended** slice — one with a step other than 1 — is strict: the right-hand side must supply exactly as many items as the slice selects, or you get `ValueError`. Finally, `lst[:] = other` replaces the contents in place, so every alias of that list sees the change, whereas `lst = other` only rebinds the name.

code

python · 7 lines
python
lst = [0, 1, 2, 3, 4]
lst[1:3] = ["a", "b", "c"]   # region replaced, length grows to 6
print(lst)
del lst[::2]                 # every other item removed
print(lst)
lst[0:1] = "hi"              # any iterable splats: two characters
print(lst)

go deeper

for a junior

Know that a list slice can appear on the left of an assignment and that del works with a slice. Recognising lst[1:3] = [...] as replacing a region, not a single element, is enough here.

for a middle

Explain the mechanics: the right-hand side is any iterable and is spliced item by item, so the length can change; insertion and deletion are the empty-region and empty-iterable cases of the same operation.

for a senior

Bring the cost and the aliasing angle. Splicing near the front shifts the tail, so it is O(n); and lst[:] = other mutating in place is a deliberate choice when callers hold references, and a bug when they do not expect it.

for a principal

Weigh readability against cleverness on a shared codebase: a splice that resizes a list mid-expression is compact but easy to misread, and a house rule about when in-place refills are allowed prevents surprise mutation of caller-owned data.

### Two different operations that share a syntax `lst[1] = x` compiles to `__setitem__(1, x)` and does one thing: it puts a new object at position 1. The length is unchanged, the object previously there is dropped, and everything else is untouched. `lst[1:3] = value` compiles to `__setitem__(slice(1, 3), value)` and is a splice: the region between the two bounds is removed and the items of `value` are put in its place. Because the count on the right need not match the count removed, the list's length can change: ```python lst = [0, 1, 2, 3, 4] lst[1:3] = ["a", "b", "c"] # removed 2, inserted 3 # lst is now [0, 'a', 'b', 'c', 3, 4] ``` That single capability — changing length in the middle — is the whole answer to the question, and everything below is a consequence of it. ### Insertion and deletion are the degenerate cases An empty target region inserts without removing: `lst[2:2] = [x, y]` puts two items before the old position 2. An empty right-hand side removes without inserting: `lst[1:3] = []` deletes that region, and `del lst[1:3]` says the same thing more directly through `__delitem__`. Recognising these two forms is what lets you read splicing code that would otherwise look cryptic. ### The right-hand side is an iterable, and that is a trap Slice assignment accepts *any* iterable and consumes it, so a generator, a `range` or a set all work. The trap is that a string is an iterable of characters: ```python lst[0:1] = "hi" # splices in 'h' and 'i', two items ``` Developers who mean "put this string at position 0" want `lst[0] = "hi"`. Assigning a non-iterable to a slice raises `TypeError` — the message says a list can only be assigned an iterable — which at least catches `lst[0:1] = 5` immediately. ### Extended slices are strict When the step is not 1, the arithmetic no longer allows a length change, because the selected positions are scattered and there is nowhere to put a surplus. So an extended-slice assignment demands an exact match: ```python lst = ["a", "c", 4] lst[::2] = [1, 2, 3] # ValueError: attempt to assign sequence of size 3 to extended slice of size 2 ``` Deletion has no such restriction: `del lst[::2]` removes every other element and is a perfectly ordinary operation. That asymmetry — assignment strict, deletion free — is a favourite follow-up. ### `lst[:] = other` versus `lst = other` This is the reason slice assignment shows up in real code that is not doing splicing at all. `lst = other` rebinds a name and leaves the original list object alone; anything else holding a reference to it — a module-level registry, an attribute another object captured, a default argument — still sees the old contents. `lst[:] = other` mutates the existing list in place, replacing all of its contents while preserving its identity, so every reference sees the new state: ```python shared = [1, 2, 3] alias = shared shared[:] = [9, 9] # alias is [9, 9] and alias is shared ``` That is the idiomatic way to refill a list other code already holds. It is also the reason a careless `lst[:] = ...` inside a helper can surprise a caller who expected the helper to be pure. ### What supports it, and what it costs Slice assignment and `del` with a slice require a **mutable** sequence. `list` and `bytearray` support them; `str` and `tuple` raise `TypeError`, because there is no way to change an immutable object in place. A custom class supports them only if it implements `__setitem__` and `__delitem__` and handles a `slice` key. Cost is worth stating at senior level: when the region's length changes, the tail of the list must shift, so a splice near the front of a large list is O(n), not O(k). Repeatedly splicing at position 0 inside a loop is quadratic, and a different structure — or building a new list once — is usually the right answer. ### How to describe it in an interview Say it in three beats. Index assignment replaces one element and the length is fixed. Slice assignment replaces a region with the items of an iterable, so insertion, deletion and resizing are all the same operation. And extended slices are the exception: with a step, the counts must match exactly, though `del` remains unrestricted.

  • Why does lst[::2] = [1, 2, 3] raise while del lst[::2] does not?
    An extended slice selects scattered positions, so there is no contiguous hole to grow or shrink; assignment therefore demands exactly as many items as positions selected and raises `ValueError` otherwise. Deletion has no such constraint — removing scattered positions is well defined however many there are — so `del lst[::2]` simply removes every other element and shortens the list.
  • When would you write lst[:] = other rather than lst = other?
    When other code already holds a reference to that list and must see the new contents — a shared registry, an attribute captured elsewhere, or a list passed into your function. `lst[:] = other` replaces the contents in place and preserves identity, so every alias observes the change; `lst = other` only rebinds the local name and leaves every other holder looking at the old contents.
  • What is the cost of splicing near the front of a large list?
    O(n), not O(k): when the replacement changes the region's length, every element after it shifts, so the work is proportional to the tail. Doing that inside a loop is quadratic. Prefer building the result once, appending at the end, or using a structure designed for cheap front operations such as `collections.deque` when the pattern is genuinely head-oriented.

saying these in an interview costs you the question

  • Thinks slice assignment cannot change the list length
  • Assigns a string to a slice expecting one element
  • Believes extended slice assignment can resize the list
  • Says del with a step is illegal
  • Confuses lst[:] = other with lst = other
  • Expects a tuple or str to support slice assignment

context