Quiz: Lists and Tuples
Test your understanding before moving on. Target: 70% or higher to proceed confidently.
Section 1: Multiple Choice (1 point each)
1. What is the output of the following code?
fruits = ["apple", "banana", "cherry"]
print(fruits[-1])
- A)
apple - B)
banana - C)
cherry - D)
IndexError
Answer
**C)** `cherry` *Why C:* Negative indexing starts from the end: `-1` is the last element, `-2` is the second-to-last, etc. *Why not A:* That would be `fruits[0]`. *Why not D:* `-1` is a valid index for any non-empty list. *Reference:* Section 8.22. Which of the following correctly adds all elements of [4, 5, 6] individually to my_list = [1, 2, 3]?
- A)
my_list.append([4, 5, 6]) - B)
my_list.extend([4, 5, 6]) - C)
my_list.insert([4, 5, 6]) - D)
my_list.add([4, 5, 6])
Answer
**B)** `my_list.extend([4, 5, 6])` *Why B:* `extend()` adds each element from the iterable individually, resulting in `[1, 2, 3, 4, 5, 6]`. *Why not A:* `append()` would add the entire list as a single element: `[1, 2, 3, [4, 5, 6]]`. *Why not C:* `insert()` requires two arguments (index, element), so this would raise a `TypeError`. *Why not D:* Lists don't have an `add()` method; that's for sets. *Reference:* Section 8.43. What does my_list.sort() return?
- A) A new sorted list
- B)
None - C) The sorted list (same object)
- D)
Trueif sorting was successful
Answer
**B)** `None` *Why B:* `.sort()` sorts the list in place and returns `None`. This is a deliberate Python design choice — methods that modify an object return `None` to signal the modification. *Why not A:* That's what `sorted(my_list)` does (built-in function, not method). *Why not C:* The list is modified, but the return value is `None`, not the list. *Reference:* Section 8.44. What is the key difference between a list and a tuple?
- A) Lists can hold any data type; tuples can only hold numbers
- B) Lists are ordered; tuples are unordered
- C) Lists are mutable; tuples are immutable
- D) Lists use square brackets; tuples use curly braces
Answer
**C)** Lists are mutable; tuples are immutable *Why C:* Mutability is the fundamental difference. Lists can be changed after creation; tuples cannot. *Why not A:* Both lists and tuples can hold any data type. *Why not B:* Both lists and tuples maintain insertion order. *Why not D:* Tuples use parentheses `()`, not curly braces `{}`. Curly braces are for dictionaries and sets. *Reference:* Section 8.75. What does enumerate() provide when iterating over a list?
- A) Only the indices
- B) Only the values
- C) Both the index and the value
- D) The total count of elements
Answer
**C)** Both the index and the value *Why C:* `enumerate()` yields `(index, element)` pairs, making it easy to track position while iterating. *Reference:* Section 8.56. What is the output of this code?
a = [1, 2, 3]
b = a
b.append(4)
print(a)
- A)
[1, 2, 3] - B)
[1, 2, 3, 4] - C)
[4, 1, 2, 3] - D)
TypeError
Answer
**B)** `[1, 2, 3, 4]` *Why B:* `b = a` creates an alias — both variables point to the same list object. Appending through `b` modifies the shared list, which `a` also references. *Why not A:* This would be true if `b` were a copy (`b = a.copy()`), but `b = a` is an alias. *Reference:* Section 8.87. Which of the following creates an independent copy of a list?
- A)
new = old - B)
new = old.copy() - C)
new = old.sort() - D)
new = old.reverse()
Answer
**B)** `new = old.copy()` *Why B:* `.copy()` creates a shallow copy — a new list with the same elements. *Why not A:* This creates an alias, not a copy. *Why not C:* `.sort()` returns `None` (it sorts in place), so `new` would be `None`. *Why not D:* `.reverse()` also returns `None`. *Reference:* Section 8.88. What is the output of the following list comprehension?
result = [x * 2 for x in range(5) if x % 2 == 0]
print(result)
- A)
[0, 2, 4, 6, 8] - B)
[0, 4, 8] - C)
[2, 6, 10] - D)
[0, 2, 4]
Answer
**B)** `[0, 4, 8]` *Why B:* `range(5)` produces `0, 1, 2, 3, 4`. The filter `if x % 2 == 0` keeps `0, 2, 4`. The expression `x * 2` doubles them to `0, 4, 8`. *Why not A:* This would be `[x * 2 for x in range(5)]` (no filter). *Why not D:* This would be `[x for x in range(5) if x % 2 == 0]` (no doubling). *Reference:* Section 8.6Section 2: True/False (1 point each)
9. sorted() modifies the original list and returns None.
Answer
**False.** `sorted()` returns a new sorted list and does NOT modify the original. The `.sort()` method modifies in place and returns `None`. *Reference:* Section 8.410. Tuples can be used as dictionary keys, but lists cannot.
Answer
**True.** Dictionary keys must be hashable (immutable). Tuples are immutable and therefore hashable. Lists are mutable and therefore not hashable. *Reference:* Section 8.711. my_list[:] creates a deep copy of my_list.
Answer
**False.** `my_list[:]` creates a shallow copy — it duplicates the outer list but the elements themselves are shared references. For nested lists, changes to inner lists in the copy will affect the original. Use `copy.deepcopy()` for a deep copy. *Reference:* Section 8.812. A single-element tuple requires a trailing comma: (42,).
Answer
**True.** Without the comma, `(42)` is just the integer `42` in parentheses. The comma is what makes it a tuple: `(42,)`. *Reference:* Section 8.7Section 3: Short Answer (2 points each)
13. Given the code below, what are the values of a, b, and c?
first, *middle, last = [10, 20, 30, 40, 50]
a = first
b = middle
c = last
Answer
- `a = 10` (first element) - `b = [20, 30, 40]` (everything between first and last, collected into a list by `*`) - `c = 50` (last element) Star unpacking (`*middle`) collects all remaining elements between `first` and `last` into a list. *Reference:* Section 8.714. What is the output of this code?
grid = [[0] * 3] * 2
grid[0][1] = 5
print(grid)
Answer
`[[0, 5, 0], [0, 5, 0]]` The `* 2` creates two references to the *same* inner list, not two independent copies. Modifying one row modifies both because they're aliases. To create independent rows, use a comprehension: `[[0] * 3 for _ in range(2)]`. *Reference:* Section 8.915. Explain the difference between remove() and pop() for lists. When would you use each?
Answer
- `remove(value)` searches for the first occurrence of `value` and removes it. Raises `ValueError` if not found. Use it when you know *what* to remove but not *where* it is. - `pop(index)` removes the element at `index` and returns it. Defaults to the last element if no index given. Raises `IndexError` if the index is out of range. Use it when you know *where* the element is, or when you need the removed value. Key difference: `remove()` searches by value; `pop()` removes by position and returns the removed element. *Reference:* Section 8.416. This code has a bug. What's wrong, and how would you fix it?
scores = [85, 92, 78, 95, 88]
sorted_scores = scores.sort()
print(f"Sorted: {sorted_scores}")
Answer
The output is `Sorted: None` because `.sort()` modifies the list in place and returns `None`. Two possible fixes:# Fix 1: Use sorted() to get a new sorted list
sorted_scores = sorted(scores)
# Fix 2: Sort in place, then use the original variable
scores.sort()
print(f"Sorted: {scores}")
*Reference:* Section 8.4
Section 4: Code Analysis (3 points each)
17. Trace through this code and determine the final values of x, y, and z:
x = [1, 2, 3, 4, 5]
y = x[1:4]
z = x
x.append(6)
y.append(7)
z[0] = 99
print(x)
print(y)
print(z)
Answer
[99, 2, 3, 4, 5, 6]
[2, 3, 4, 7]
[99, 2, 3, 4, 5, 6]
Step by step:
1. `x = [1, 2, 3, 4, 5]` — creates a list
2. `y = x[1:4]` — creates a NEW list `[2, 3, 4]` (slicing creates a copy)
3. `z = x` — `z` is an alias for `x` (same object)
4. `x.append(6)` — `x` (and `z`) becomes `[1, 2, 3, 4, 5, 6]`
5. `y.append(7)` — `y` becomes `[2, 3, 4, 7]` (independent)
6. `z[0] = 99` — modifies the shared list; `x` (and `z`) becomes `[99, 2, 3, 4, 5, 6]`
Key insight: `y` is independent (created by slicing); `z` is an alias (created by assignment).
*Reference:* Section 8.8
18. What does this function do? Describe it in one sentence. What does it return for mystery([3, 1, 4, 1, 5, 9, 2, 6])?
def mystery(lst):
result = []
for item in lst:
if item not in result:
result.append(item)
return result
Answer
The function removes duplicates from a list while preserving the original order of first appearances. For `mystery([3, 1, 4, 1, 5, 9, 2, 6])`, it returns `[3, 1, 4, 5, 9, 2, 6]`. The `1` at index 3 is skipped because `1` is already in `result` from index 1. Note: This is an O(n^2) approach because `item not in result` scans the entire result list each time. In [Chapter 9](../chapter-09-dictionaries-and-sets/index.md), you'll learn about sets, which make this O(n). *Reference:* Sections 8.5, 8.1019. The following function is supposed to remove all negative numbers from a list. It has a bug. Find it, explain why it fails, and fix it.
def remove_negatives(numbers):
for i in range(len(numbers)):
if numbers[i] < 0:
numbers.pop(i)
return numbers
print(remove_negatives([1, -2, 3, -4, 5, -6]))
Answer
**Bug:** Modifying a list while iterating over it by index causes skipped elements. When a negative number is removed, all subsequent elements shift left, but `i` continues incrementing, so the element that moved into position `i` is never checked. This also causes an `IndexError` when `i` exceeds the shortened list's length. **Fix 1: Iterate backwards**def remove_negatives(numbers):
for i in range(len(numbers) - 1, -1, -1):
if numbers[i] < 0:
numbers.pop(i)
return numbers
**Fix 2: Create a new list (preferred)**
def remove_negatives(numbers):
return [n for n in numbers if n >= 0]
**Fix 3: Use a while loop with manual index control**
def remove_negatives(numbers):
i = 0
while i < len(numbers):
if numbers[i] < 0:
numbers.pop(i)
else:
i += 1
return numbers
*Reference:* Sections 8.4, 8.8
20. What does this list comprehension produce? Break it down step by step.
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = [row[i] for i in range(3) for row in matrix]
print(result)