Question bank — Lists — creation, indexing, slicing, mutability
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.
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.
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.
a.append(x) returns the new, longer list.
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.
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.
a[:] is a is False, even though a[:] == a is True.Index -1 and index n-1 always refer to the same box.
-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.
[1, "hi", 3.5, True]. "Ordered" constrains position, not type.The empty list [] is invalid because it has nothing to order.
[] 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.
== 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?
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]?
[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?
-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?
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?
=. 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?
[] — still a distinct object from a. Copying zero elements is valid, not an error.What does a[5:5] return for a normal 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?
[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?
[]. 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?
[]. 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?
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.