1.2.21 · D5Introduction to Programming (Python)

Question bank — Lists — creation, indexing, slicing, mutability

1,366 words6 min readBack to topic

This bank is pure reasoning — no heavy arithmetic (that lives in D3/D4). Every answer explains why, never just yes/no.


True or false — justify

b = a makes an independent copy of the list a.
False. It binds a second name to the same list object; editing through b is visible through a. Real copies need a[:], a.copy(), or list(a).
Assigning x = 5; y = x; y = 6 leaves x at 5.
True — but not because numbers are copied. Integers are immutable, so y = 6 rebinds y to a new object; it never mutates the shared one. This is why = feels like copying even though it isn't.
Slicing with an out-of-range stop, like a[1:100], raises IndexError.
False. Slices describe a range of gaps and Python clamps them to what exists, returning whatever's inside. Only single-index access must point at one real box, so only that raises.
a.append(x) returns the new, longer list.
False. It mutates a in place and returns None. Writing a = a.append(x) therefore silently sets a to None.
a.append(x) and a = a + [x] are interchangeable.
False. append edits the existing list (visible to aliases); + builds a brand-new list and rebinds the name (aliases keep the old one). Same output, different object identity.
A slice a[:] returns the same object as a.
False. A full slice copies every element into a fresh list. a[:] is a is False, even though a[:] == a is True.
Index -1 and index n-1 always refer to the same box.
True. -k means "k steps back from the end," which sits at forward position n-k; with k=1 that's n-1, the last element.
Lists can only hold one type of item.
False. A single list may mix types freely, e.g. [1, "hi", 3.5, True]. "Ordered" constrains position, not type.
The empty list [] is invalid because it has nothing to order.
False. [] is a perfectly valid list of length 0; it just has no boxes yet. len([]) is 0 and you can append to it.
a == b being True guarantees editing a also changes b.
False. == compares contents, not identity. Two separate lists with equal contents are equal but independent; only shared identity (a is b) links their edits.

Spot the error

a = [1, 2, 3]
a = a.append(4)
print(a[0])

::: append returns None, so a is rebound to None and a[0] raises TypeError (None isn't subscriptable). Fix: just a.append(4) with no assignment.

scores = [10, 20, 30]
backup = scores          # "save a copy before editing"
scores[0] = 99
print(backup[0])         # expecting 10

::: backup is the same list, not a copy, so it prints 99. The comment lies. Use backup = scores[:] (or .copy()) for a real snapshot.

a = ['p', 'y', 't', 'h']
print(a[4])

::: IndexError. Valid indices are 0..n-1, i.e. 0..3 for length 4; 4 points past the last box. (Note a[4:] would be fine — an empty slice.)

a = [0, 1, 2, 3, 4]
print(a[3:1])

::: Not an error — it prints []. With a positive step, start >= stop means "no boxes in this range." To walk backward you must supply a negative step, e.g. a[3:1:-1].

a = [1, 2, 3]
print(a[-4])

::: IndexError. -4 maps to n-4 = 3-4 = -1, which is out of range — negative indices are only valid down to -n (here -3). Going one further falls off the front.

row = [0] * 3
grid = [row] * 2
grid[0][0] = 9
print(grid[1][0])        # expecting 0

::: Prints 9. [row] * 2 stores the same inner list twice, so editing one row shows in "both." This aliasing trap is why you build nested lists with a loop/comprehension instead (see Shallow vs Deep copy).


Why questions

Why does indexing start at 0 instead of 1?
An index is an offset (distance from the start), not a count. The first element is 0 steps in, so "jump k forward" is just start + k — clean arithmetic with no off-by-one correction.
Why is stop excluded from a slice a[start:stop]?
Indices mark the gaps between boxes, so a slice cuts at two gaps. Half-open [start, stop) makes the length exactly stop - start, and lets a[:k] and a[k:] rejoin with no overlap and no gap.
Why does a[::-1] reverse a list?
The step -1 walks the boxes backward from the end toward the start, visiting every one in reverse order — reversal falls out for free, no special "reverse" rule needed.
Why does slicing forgive bad ranges while indexing doesn't?
Indexing must resolve to one real element, so an impossible position is an error. A slice only names a span, which Python clamps to what actually exists — an empty span is still a valid answer.
Why does b = a share the list but b = a[:] copy it?
b = a copies the address (label points at the same box). a[:] first builds a new list from the elements, then binds b to that new box — different object, independent edits.
Why does the aliasing trap hit lists but not integers or strings?
Both share via =. But integers/strings are immutable: any "change" rebinds to a new object, so the alias never sees it. Lists are mutable, so an in-place edit is visible through every name pointing at that list. See Mutability & function arguments.

Edge cases

What does a[:] do when a is empty?
Returns a new empty list [] — still a distinct object from a. Copying zero elements is valid, not an error.
What does a[5:5] return for a normal list?
An empty list []. start == stop means zero boxes span the range, regardless of whether index 5 exists (the range is clamped anyway).
What is the result of a[::-1] on a one-element or empty list?
A new list identical in contents ([x] or []). Reversing zero or one element changes nothing but still produces a fresh object.
Is a[100:200] on a short list an error?
No — it returns []. Both bounds are clamped to the list's end, leaving an empty span. Contrast a[100], which would raise IndexError.
For length n, what is the full valid range of a single index?
-n up to n-1 inclusive. Positive side 0..n-1 names boxes from the front; negative side -1..-n names the same boxes from the back. Anything outside raises IndexError.
What happens with a[start:stop] when start > stop and step is positive?
You get []. The step marches upward from start, immediately overshoots stop, and lands on nothing — an empty (not reversed) slice.
Does list.sort() return the sorted list?
No — like append, sort() mutates in place and returns None. Use sorted(a) when you want a new sorted list and keep the original. See List methods — append, insert, pop, sort.

Recall One-line trap summary

Cover this and recite the six deadliest traps. Six traps ::: (1) = shares, doesn't copy; (2) append/sort return None; (3) slices clamp, indices raise; (4) stop is excluded; (5) negatives valid only to -n; (6) [row]*k aliases the inner list.