Foundation Quiz
Quiz
a = [1, 2, 3] followed by b = a and then b.append(4)?b = a creates a reference to the same list object, not a copy. When you modify b, you’re modifying the same object that a references. To create an independent copy, use b = a.copy().b = a creates a reference to the same list object, not a copy. When you modify b, you’re modifying the same object that a references. To create an independent copy, use b = a.copy().False in a boolean context?0, 0.0, 0j), empty sequences ("", [], (), {}), None, and False. Note that [0] is a non-empty list containing one element, so it’s truthy. Similarly, "0" (the string containing the character zero) is truthy because it is a non-empty string — only the empty string "" is falsy.0, 0.0, 0j), empty sequences ("", [], (), {}), None, and False. Note that [0] is a non-empty list containing one element, so it’s truthy. Similarly, "0" (the string containing the character zero) is truthy because it is a non-empty string — only the empty string "" is falsy.1 < x < 10 is equivalent to 1 < x and x < 10 in Python.1 < x < 10 is syntactic sugar for 1 < x and x < 10, making code more readable.1 < x < 10 is syntactic sugar for 1 < x and x < 10, making code more readable.x = 5
x += 3
x *= 2
print(x)x = 5, then x += 3 makes x = 8, then x *= 2 makes x = 16. Assignment operators modify the variable in place.x = 5, then x += 3 makes x = 8, then x *= 2 makes x = 16. Assignment operators modify the variable in place.:=) allows you to assign a value to a variable and use that value in the same expression, e.g., if (n := len(data)) > 10::=) allows you to assign a value to a variable and use that value in the same expression, e.g., if (n := len(data)) > 10:for i in range(3):
count = 0
count += i
print(count, end=' ')count is declared inside the loop, so it resets to 0 on every iteration. Then it adds i (0, 1, 2) and prints. If count were declared outside the loop, it would accumulate values.count is declared inside the loop, so it resets to 0 on every iteration. Then it adds i (0, 1, 2) and prints. If count were declared outside the loop, it would accumulate values.count is initialized—inside or outside the loop?None?is (not ==) for None checks. is checks object identity, while == checks value equality. Since None is a singleton (there is exactly one None object in memory), is None is the correct and idiomatic approach.is (not ==) for None checks. is checks object identity, while == checks value equality. Since None is a singleton (there is exactly one None object in memory), is None is the correct and idiomatic approach.False and expensive_function() will execute expensive_function().and is False, the result is already known to be False, so Python never evaluates the right side. This is an optimization that can prevent unnecessary computations.and is False, the result is already known to be False, so Python never evaluates the right side. This is an optimization that can prevent unnecessary computations.What is the time complexity of this code?
for i in range(n):
for j in range(n):
print(i, j)n times. The outer loop runs n times, and for each iteration of the outer loop, the inner loop also runs n times. Total operations: n × n = n². This is quadratic time complexity.n times. The outer loop runs n times, and for each iteration of the outer loop, the inner loop also runs n times. Total operations: n × n = n². This is quadratic time complexity.fruits = ['apple', 'banana', 'cherry']
for index, fruit in _____(fruits):
print(f"{index}: {fruit}")enumerate() function returns both the index and value while iterating over a sequence. It’s more Pythonic than using range(len(fruits)) and manually indexing.enumerate() function returns both the index and value while iterating over a sequence. It’s more Pythonic than using range(len(fruits)) and manually indexing.The Walrus Operator (:=)
Introduced in Python 3.8+, it allows you to assign a value to a variable AND use that value in the same expression.
General Form:
variable := expressionExample:
if (n := len(data)) > 10:
print(f"List has {n} elements")This evaluates len(data), assigns it to n, and uses it in the comparison—all in one line.
Did you get it right?
else clause with loops in Python?for and while loops can have an else clause that executes only when the loop completes normally without encountering a break. This is useful for search operations where you want to know if the search completed without finding the target.for and while loops can have an else clause that executes only when the loop completes normally without encountering a break. This is useful for search operations where you want to know if the search completed without finding the target.result = 0 or 5 or 10
print(result)or operator returns the first truthy value. Since 0 is falsy, it skips to 5, which is truthy, and returns it immediately (short-circuit evaluation). The value 10 is never evaluated.or operator returns the first truthy value. Since 0 is falsy, it skips to 5, which is truthy, and returns it immediately (short-circuit evaluation). The value 10 is never evaluated.or returns the first truthy value, not True/False.timeout = seconds or 30 when seconds can legitimately be 0?0 is falsy in Python, 0 or 30 evaluates to 30. If 0 is a valid timeout value (e.g., no timeout), this breaks the logic. The fix is: timeout = 30 if seconds is None else seconds, which explicitly checks for None only.0 is falsy in Python, 0 or 30 evaluates to 30. If 0 is a valid timeout value (e.g., no timeout), this breaks the logic. The fix is: timeout = 30 if seconds is None else seconds, which explicitly checks for None only.x, y = 10, 20
x, y = y, x
print(x, y)(y, x) is evaluated first as a tuple (20, 10), then unpacked to x, y. No temporary variable needed!(y, x) is evaluated first as a tuple (20, 10), then unpacked to x, y. No temporary variable needed!Short-Circuit Evaluation
Python stops evaluating a logical expression as soon as the final result is determined.
With and:
- If left side is
False, result must beFalse—don’t evaluate right side False and expensive_function()→ Never calls function
With or:
- If left side is
True, result must beTrue—don’t evaluate right side True or expensive_function()→ Never calls function
This is both an optimization and a useful programming pattern for avoiding errors (e.g., x and x.method() won’t fail if x is None).
Did you get it right?
original = [1, 2, 3]
independent = original._____()
independent.append(4).copy() method creates a shallow copy of a list, making it independent from the original. Modifying the copy won’t affect the original. Alternatively, you could use independent = original[:] or independent = list(original)..copy() method creates a shallow copy of a list, making it independent from the original. Modifying the copy won’t affect the original. Alternatively, you could use independent = original[:] or independent = list(original).range() function are correct?range() uses zero-based indexing and excludes the stop value. range(5) gives 0-4, range(2, 7) gives 2-6 (not 7). The third parameter is step size: range(0, 10, 2) gives 0, 2, 4, 6, 8. Modern Python returns a range object (not a list) for memory efficiency.range() uses zero-based indexing and excludes the stop value. range(5) gives 0-4, range(2, 7) gives 2-6 (not 7). The third parameter is step size: range(0, 10, 2) gives 0, 2, 4, 6, 8. Modern Python returns a range object (not a list) for memory efficiency.for num in [1, 2, 3, 4, 5]:
if num % 2 == 0:
continue
print(num, end=' ')continue statement skips the rest of the current iteration and moves to the next one. When num is even (2, 4), it skips the print statement. Only odd numbers (1, 3, 5) are printed.continue statement skips the rest of the current iteration and moves to the next one. When num is even (2, 4), it skips the print statement. Only odd numbers (1, 3, 5) are printed.continue skips to the next iteration.isinstance(3.14, (int, float)) returns True.isinstance() can check against a tuple of types, returning True if the value matches any type in the tuple. Since 3.14 is a float, and float is in the tuple (int, float), it returns True.isinstance() can check against a tuple of types, returning True if the value matches any type in the tuple. Since 3.14 is a float, and float is in the tuple (int, float), it returns True.The Accumulator Pattern
A fundamental programming pattern that builds up a result through iteration:
- Initialize a variable before the loop (e.g.,
total = 0) - Update it inside the loop (e.g.,
total += num) - Use the final result after the loop
Examples:
# Sum accumulator
total = 0
for num in numbers:
total += num
# List accumulator
squares = []
for i in range(1, 6):
squares.append(i ** 2)The key is that the variable is declared outside the loop so it persists across iterations.
Did you get it right?
In the nested loops below, how many times does the inner print statement execute?
for i in range(3):
for j in range(4):
print(i, j)numbers = [1, 2, 3, 4, 5]
for num in numbers:
if num == 3:
print("Found")
break
else:
print("Not found")break, which exits the loop. Since break was called, the else clause is skipped. Only ‘Found’ is printed. The else clause only runs when the loop completes normally without break.break, which exits the loop. Since break was called, the else clause is skipped. Only ‘Found’ is printed. The else clause only runs when the loop completes normally without break.else clause is skipped when break is executed.continue keyword skips the remaining code in the current loop iteration and jumps to the next iteration. It’s different from break, which exits the loop entirely.continue keyword skips the remaining code in the current loop iteration and jumps to the next iteration. It’s different from break, which exits the loop entirely.break or pass.names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]
for name, score in _____(names, scores):
print(f"{name}: {score}")zip() function takes multiple iterables and returns an iterator of tuples, pairing up elements from each iterable. It’s perfect for parallel iteration over multiple sequences.zip() function takes multiple iterables and returns an iterator of tuples, pairing up elements from each iterable. It’s perfect for parallel iteration over multiple sequences.== and is in Python?== and is in Python?== vs is
== (Equality Operator):
- Compares values
- Checks if two objects have the same content
- Example:
[1, 2, 3] == [1, 2, 3]→True
is (Identity Operator):
- Compares object identity (memory address)
- Checks if two variables reference the exact same object
- Example:
a = [1, 2, 3]; b = [1, 2, 3]; a is b→False
Best Practice:
- Use
isfor singleton objects likeNone:x is None - Use
==for value comparisons
Did you get it right?
You want code to run only when both of these are true:
reason == 'Scheduled'- Either
s_time is Noneorts < s_time
Which condition is correct?
and binds tighter than or, reason == 'Scheduled' and s_time is None or ts < s_time is parsed as (reason == 'Scheduled' and s_time is None) or ts < s_time. This causes the condition to fire whenever ts < s_time, regardless of reason. Explicit parentheses around the or subexpression are required.and binds tighter than or, reason == 'Scheduled' and s_time is None or ts < s_time is parsed as (reason == 'Scheduled' and s_time is None) or ts < s_time. This causes the condition to fire whenever ts < s_time, regardless of reason. Explicit parentheses around the or subexpression are required.and has higher precedence than or—it groups first, just like * before +.print(15 / 4)
print(15 // 4)/ (true division) always returns a float, even when dividing two integers: 15 / 4 = 3.75. The // operator is floor division — it rounds down to the nearest integer: 15 // 4 = 3. This differs from Python 2, where 15 / 4 returned 3. A common bug: expecting an integer from / and getting unexpected decimal results in downstream calculations./ (true division) always returns a float, even when dividing two integers: 15 / 4 = 3.75. The // operator is floor division — it rounds down to the nearest integer: 15 // 4 = 3. This differs from Python 2, where 15 / 4 returned 3. A common bug: expecting an integer from / and getting unexpected decimal results in downstream calculations./ works compared to Python 2 — it no longer truncates.value_if_true if condition else value_if_false. Option B is C/JavaScript ternary syntax (?:), which Python does not support. Option C uses statement syntax (with colons) inside an expression context, which is a SyntaxError. Option D uses when, which is not a Python keyword. Only option A is valid Python.value_if_true if condition else value_if_false. Option B is C/JavaScript ternary syntax (?:), which Python does not support. Option C uses statement syntax (with colons) inside an expression context, which is a SyntaxError. Option D uses when, which is not a Python keyword. Only option A is valid Python.name = user_input or 'Anonymous' will set name to 'Anonymous' when user_input is an empty string "", even if an empty string was intentional."" is falsy in Python, so "" or 'Anonymous' evaluates to 'Anonymous' — the exact same mechanism as 0 or 30 returning 30. Both 0 and "" are falsy, so the or default pattern silently replaces them. The fix is an explicit None check: name = 'Anonymous' if user_input is None else user_input, which substitutes only when the value is truly absent."" is falsy in Python, so "" or 'Anonymous' evaluates to 'Anonymous' — the exact same mechanism as 0 or 30 returning 30. Both 0 and "" are falsy, so the or default pattern silently replaces them. The fix is an explicit None check: name = 'Anonymous' if user_input is None else user_input, which substitutes only when the value is truly absent.or defaults.count = 0
for i in range(5):
if i == 2:
pass
count += 1
print(count)pass is a true no-op — it does absolutely nothing, and execution falls through to the next statement. When i == 2, pass executes (doing nothing), then count += 1 still runs. All 5 iterations increment count, giving 5. If continue were used instead, it would skip count += 1 for i == 2, giving 4. The critical distinction: pass fills syntactic space; continue redirects control flow.pass is a true no-op — it does absolutely nothing, and execution falls through to the next statement. When i == 2, pass executes (doing nothing), then count += 1 still runs. All 5 iterations increment count, giving 5. If continue were used instead, it would skip count += 1 for i == 2, giving 4. The critical distinction: pass fills syntactic space; continue redirects control flow.pass does nothing at all — it’s a placeholder, not a loop control statement.d = {'name': 'Alice', 'age': 30}?for x in d) yields its keys only, not key-value pairs — so for key, value in d fails with ValueError: too many values to unpack. d.keys() returns keys, not values, making option E semantically wrong (it yields keys while the variable is named value). The three correct patterns: for key in d: (keys), for value in d.values(): (values), for key, value in d.items(): (both).for x in d) yields its keys only, not key-value pairs — so for key, value in d fails with ValueError: too many values to unpack. d.keys() returns keys, not values, making option E semantically wrong (it yields keys while the variable is named value). The three correct patterns: for key in d: (keys), for value in d.values(): (values), for key, value in d.items(): (both)..items() for key-value pairs.match-case statement, execution automatically falls through from a matched case to the next one, similar to switch in C or Java.switch, Python’s match-case does not fall through. Only the first matching case executes, then control exits the match block entirely. In C, you need explicit break to prevent fall-through; in Python, there is no fall-through at all — each case is fully isolated. This makes Python’s structural pattern matching safer by default.switch, Python’s match-case does not fall through. Only the first matching case executes, then control exits the match block entirely. In C, you need explicit break to prevent fall-through; in Python, there is no fall-through at all — each case is fully isolated. This makes Python’s structural pattern matching safer by default.'age' in {'name': 'Alice', 'age': 30} return?in operator on a dictionary tests keys, not values. 'age' in d is equivalent to 'age' in d.keys(). To check values, use 30 in d.values(). To check a key-value pair, use ('age', 30) in d.items(). This is intentional: key lookup is O(1) via hashing; scanning values would require an O(n) sweep.in operator on a dictionary tests keys, not values. 'age' in d is equivalent to 'age' in d.keys(). To check values, use 30 in d.values(). To check a key-value pair, use ('age', 30) in d.items(). This is intentional: key lookup is O(1) via hashing; scanning values would require an O(n) sweep.for x in d iterates over — that’s what in also checks.match-case statement, what does case _: do?case _: is the wildcard pattern — it matches anything and is equivalent to the else in an if-elif-else chain. Unlike case x: (which captures the matched value into variable x), _ discards the matched value and doesn’t bind it. The wildcard doesn’t require _ to be defined beforehand — it’s pattern syntax, not a variable lookup. It must be the last case, or Python raises SyntaxError for unreachable patterns after it.case _: is the wildcard pattern — it matches anything and is equivalent to the else in an if-elif-else chain. Unlike case x: (which captures the matched value into variable x), _ discards the matched value and doesn’t bind it. The wildcard doesn’t require _ to be defined beforehand — it’s pattern syntax, not a variable lookup. It must be the last case, or Python raises SyntaxError for unreachable patterns after it._ convention means ‘anything I don’t care about’ — same as in tuple unpacking like a, _ = (1, 2).count = 3
while count > 0:
print(count, end=' ')
count -= 1count > 0. Starting at 3: prints 3, decrements to 2 → prints 2, decrements to 1 → prints 1, decrements to 0 → condition 0 > 0 is False, loop exits. The value 0 is never printed because the condition is evaluated before each iteration, not after. Option B is a common off-by-one mistake from thinking the check happens after the body.count > 0. Starting at 3: prints 3, decrements to 2 → prints 2, decrements to 1 → prints 1, decrements to 0 → condition 0 > 0 is False, loop exits. The value 0 is never printed because the condition is evaluated before each iteration, not after. Option B is a common off-by-one mistake from thinking the check happens after the body.while condition is evaluated before the loop body — if it’s false, the body doesn’t run at all.Which of these two functions has O(n) space complexity?
# Function A
def sum_list(arr):
total = 0
for num in arr:
total += num
return total
# Function B
def double_list(arr):
result = []
for num in arr:
result.append(num * 2)
return resulttotal) regardless of input size — that’s O(1) extra space. Function B builds a new result list that grows to n elements — that’s O(n) extra space. The input array doesn’t count toward either function’s space complexity; it was already in memory before the function ran.total) regardless of input size — that’s O(1) extra space. Function B builds a new result list that grows to n elements — that’s O(n) extra space. The input array doesn’t count toward either function’s space complexity; it was already in memory before the function ran.sorted() uses Timsort (O(n log n) worst case) rather than naive Quick Sort, and why knowing your data’s shape matters when choosing a sort algorithm.sorted() uses Timsort (O(n log n) worst case) rather than naive Quick Sort, and why knowing your data’s shape matters when choosing a sort algorithm.blocklist (a list) with 100,000 items. For each of 50,000 urls, you check if url in blocklist. What is the total time complexity of all 50,000 checks, and what is the simplest fix?in) on a list is O(n) — Python scans each element sequentially. Doing 50,000 O(n) checks gives O(n × n) = O(n²) total. Converting to a set uses a hash table, making each in check O(1) average case. The one-time conversion cost is O(n), so all 50,000 checks together cost O(n) total — a dramatic improvement. Binary search (option D) would give O(n log n) but requires keeping the list sorted; using a set is both simpler and faster.in) on a list is O(n) — Python scans each element sequentially. Doing 50,000 O(n) checks gives O(n × n) = O(n²) total. Converting to a set uses a hash table, making each in check O(1) average case. The one-time conversion cost is O(n), so all 50,000 checks together cost O(n) total — a dramatic improvement. Binary search (option D) would give O(n log n) but requires keeping the list sorted; using a set is both simpler and faster.