Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Python›Quiz

Python quiz

Ten questions at a time, drawn from 390. Every answer is explained. Nothing is saved and no account is needed.

Question 1 of 10Score 0

What is the result of the expression: 10 + 5 * 2?

Practice quiz for Python. Scores are not saved.

Study first?

Every question comes from a lesson in the Python course.

Read the course →

Interview prep

Written questions with full answers.

Python interview questions →

All Python quiz questions and answers

  1. What is the result of the expression: 10 + 5 * 2?

    • 30
    • 20
    • 25
    • 15

    Answer: 20. Python follows operator precedence (PEMDAS/BODMAS). Multiplication happens before addition, so 5*2 is 10, plus 10 equals 20. Incorrect options result from processing left-to-right or other order errors.

    From lesson: Introduction to Python

  2. Which of the following is true about lists in Python?

    • They are immutable
    • They are ordered collections of items
    • They can only contain one data type
    • They use parentheses for definition

    Answer: They are ordered collections of items. Lists are mutable and ordered. They can hold mixed data types and are defined using square brackets, not parentheses.

    From lesson: Introduction to Python

  3. What happens when you execute 'print(type(5.0))'?

    • <class 'int'>
    • <class 'str'>
    • <class 'float'>
    • <class 'number'>

    Answer: <class 'float'>. The presence of a decimal point signifies a floating-point number. Integers have no decimal, strings are quoted text, and 'number' is not a base Python type.

    From lesson: Introduction to Python

  4. Given the code: x = [1, 2, 3]; y = x; x[0] = 99; what is the value of y[0]?

    • 1
    • 99
    • Error
    • None

    Answer: 99. In Python, variables for lists are references to the same object in memory. Modifying x affects the object that y also points to. Other answers assume copies are created, which is not the case for simple assignment.

    From lesson: Introduction to Python

  5. How do you correctly define a function named 'greet' that takes no arguments?

    • def greet():
    • function greet()
    • define greet:
    • def greet:

    Answer: def greet():. The 'def' keyword is required, followed by the name, parentheses for arguments, and a colon. Other options use incorrect keywords or omit mandatory parentheses.

    From lesson: Introduction to Python

  6. What is the output of print(type(1 / 2))?

    • <class 'int'>
    • <class 'float'>
    • <class 'decimal'>
    • Error

    Answer: <class 'float'>. In Python 3, the single forward slash operator performs float division, even if both numbers are integers. Options 0 and 2 are incorrect because the result is a float, and option 3 is wrong because this is valid syntax.

    From lesson: Variables and Data Types

  7. Which of the following variable assignments will raise a SyntaxError?

    • my_var = 10
    • 2nd_value = 20
    • _total = 100
    • dataValue = 'test'

    Answer: 2nd_value = 20. Variable names cannot start with a number in Python. All other options are valid variable identifiers. Option 0, 2, and 3 are correct naming conventions.

    From lesson: Variables and Data Types

  8. If x = '5', what is the result of x + 5?

    • 10
    • 55
    • TypeError
    • None

    Answer: TypeError. Python does not allow implicit type conversion between strings and integers during addition. Option 0 and 1 are incorrect because they assume string conversion or integer math occurs automatically. Option 3 is wrong because an exception is raised.

    From lesson: Variables and Data Types

  9. Which statement best describes the mutability of a Python tuple?

    • Tuples are mutable and can be changed after creation.
    • Tuples are immutable and cannot be changed after creation.
    • Tuples only become immutable when they contain only integers.
    • Tuples can only be changed if they have more than one element.

    Answer: Tuples are immutable and cannot be changed after creation.. Tuples are defined as immutable sequences. Once created, their elements cannot be modified, added, or removed. Options 0, 2, and 3 are incorrect because mutability is a property of the data type itself, not dependent on content or size.

    From lesson: Variables and Data Types

  10. What is the result of print(bool('False'))?

    • False
    • True
    • 0
    • TypeError

    Answer: True. The bool() function returns True for any non-empty string, regardless of the text contained within it. Therefore, 'False' as a string is truthy. Options 0, 2, and 3 are incorrect because they misunderstand the truthiness of non-empty strings.

    From lesson: Variables and Data Types

  11. What is the result of the expression: 10 // 3 + 1?

    • 4.33
    • 3
    • 4
    • 4.0

    Answer: 4. Floor division // takes precedence over addition. 10 // 3 is 3, and 3 + 1 is 4. Option 0 is the result of true division, option 1 is the floor result without the addition, and option 3 is a float, which the // operator does not return when applied to integers.

    From lesson: Operators (Arithmetic, Comparison, Logical)

  12. What is the output of: 5 > 3 and 2 < 4?

    • True
    • False
    • 2
    • 5

    Answer: True. Both comparisons evaluate to True. 'True and True' results in True. Option 1 is incorrect because the condition is met. Options 2 and 3 are incorrect because logical operators return the boolean result of the expression when both operands evaluate to boolean True/False.

    From lesson: Operators (Arithmetic, Comparison, Logical)

  13. What is the value of: 10 or 20?

    • True
    • False
    • 10
    • 20

    Answer: 10. The 'or' operator returns the first truthy value. Since 10 is non-zero (truthy), it is returned immediately. Options 0 and 1 are incorrect because the operator returns the operand, not a boolean, and option 3 is ignored due to short-circuiting.

    From lesson: Operators (Arithmetic, Comparison, Logical)

  14. What is the result of the expression: 5 == 5.0?

    • True
    • False
    • None
    • Error

    Answer: True. Python compares the numeric value of 5 and 5.0, finding them equal. Option 1 is wrong because numeric equality ignores type differences. Options 2 and 3 are wrong because this is a valid arithmetic comparison.

    From lesson: Operators (Arithmetic, Comparison, Logical)

  15. Which of the following is equivalent to: not (x == y or x > z)?

    • not x == y or x > z
    • x != y and x <= z
    • x != y or x <= z
    • not x == y and not x > z

    Answer: x != y and x <= z. Applying De Morgan's Law, 'not (A or B)' is 'not A and not B'. Thus, 'not (x == y)' is 'x != y' and 'not (x > z)' is 'x <= z'. Option 0 lacks grouping, option 2 uses the wrong logical operator, and option 3 is equivalent but less idiomatic than using direct inequality operators.

    From lesson: Operators (Arithmetic, Comparison, Logical)

  16. What is the result of 'Python'.replace('P', 'J')?

    • Jython
    • PythonJ
    • Python
    • TypeError: 'str' object does not support item assignment

    Answer: Jython. The correct answer is 'Jython' because .replace() creates a new string with the specified substitutions. Option 2 is wrong because 'P' is not at the end, Option 3 is wrong because strings are immutable but .replace() returns a copy, and Option 4 is wrong because .replace() is a valid method, not an assignment operation.

    From lesson: Strings and String Methods

  17. Given s = ' Hello World ', what is the result of s.strip().split(' ')?

    • ['', 'Hello', 'World', '']
    • ['Hello', 'World']
    • ['Hello', '', 'World']
    • [' Hello', 'World ']

    Answer: ['Hello', 'World']. The strip() method removes the outer whitespace, resulting in 'Hello World'. Splitting this string by a single space results in a list of two words. Option 1 is wrong because strip removes the outer spaces, Option 3 is wrong because there is only one space between the words, and Option 4 ignores the effect of strip.

    From lesson: Strings and String Methods

  18. Which of the following expressions evaluates to True?

    • 'cat' == 'Cat'
    • 'apple' > 'Apple'
    • 'a' in 'banana'
    • 'abc'.upper() == 'abc'

    Answer: 'a' in 'banana'. Option 3 is true because 'a' exists within 'banana'. Option 1 is false due to case sensitivity. Option 2 is false because uppercase letters have lower Unicode values than lowercase letters. Option 4 is false because 'abc'.upper() returns 'ABC', which is not equal to 'abc'.

    From lesson: Strings and String Methods

  19. If s = 'Data', what is the output of s[1:3]?

    • 'Da'
    • 'at'
    • 'ata'
    • 'Dat'

    Answer: 'at'. Slicing in Python is inclusive of the start index and exclusive of the end index. Index 1 is 'a' and index 2 is 't'. Option 1 is wrong as it slices indices 0 and 1. Option 3 includes index 3, and Option 4 includes index 0.

    From lesson: Strings and String Methods

  20. What does '123'.isdigit() return?

    • True
    • False
    • 123
    • None

    Answer: True. The .isdigit() method returns True if all characters in the string are digits. Option 2 is wrong because the string contains only numeric characters. Option 3 is wrong because the method returns a boolean, not the string itself, and Option 4 is wrong because the method is valid for strings of digits.

    From lesson: Strings and String Methods

  21. If you write age = input('Enter age: '), what is the data type of the variable 'age'?

    • int
    • float
    • str
    • None

    Answer: str. The input() function always captures user keystrokes as a string. Option 0 and 1 are wrong because conversion requires int() or float(). Option 3 is wrong because input() will always return something, even if it is an empty string.

    From lesson: User Input and Output

  22. Which of the following is the most idiomatic way to display the variable 'x' inside a string in modern Python?

    • print('Value is ' + x)
    • print(f'Value is {x}')
    • print('Value is', x)
    • print('Value is %s' % x)

    Answer: print(f'Value is {x}'). f-strings (Option 1) are the modern, readable standard. Option 0 causes errors if x is not a string. Option 2 adds an unwanted space. Option 3 uses outdated C-style formatting.

    From lesson: User Input and Output

  23. What happens if you run print('A', 'B', sep='-')?

    • AB
    • A-B
    • A B
    • TypeError

    Answer: A-B. The 'sep' parameter defines what character sits between the arguments. Option 0 is the default if sep were empty, Option 2 is the default behavior, and Option 3 is incorrect as this is valid syntax.

    From lesson: User Input and Output

  24. What is the result of print('Hello', end='!')?

    • Prints Hello and moves to a new line
    • Prints Hello! without a new line
    • Causes a syntax error
    • Prints Hello followed by a newline and then an exclamation point

    Answer: Prints Hello! without a new line. The 'end' parameter replaces the default newline character. Option 0 describes the default behavior. Option 2 is incorrect syntax-wise. Option 3 misinterprets how the end parameter functions.

    From lesson: User Input and Output

  25. How do you correctly read two numbers entered on a single line separated by a space into two separate variables?

    • a, b = input().split()
    • a = input(); b = input()
    • a, b = input()
    • a = int(input()); b = int(input())

    Answer: a, b = input().split(). split() breaks a string into a list based on whitespace. Option 1 expects two separate lines. Option 2 attempts to unpack a single string into two variables without splitting, causing an error. Option 3 expects two lines.

    From lesson: User Input and Output

  26. What is the result of the expression int(3.9) + int(-3.9)?

    • 0
    • -1
    • 1
    • Error

    Answer: 0. int() truncates towards zero. int(3.9) becomes 3 and int(-3.9) becomes -3. 3 + -3 equals 0. It is not rounding, so -4 or 4 are incorrect.

    From lesson: Type Conversion and Casting

  27. Which of the following boolean evaluations is True?

    • bool(0.0)
    • bool([])
    • bool('False')
    • bool(None)

    Answer: bool('False'). In Python, non-empty strings are truthy, even if the content is 'False'. 0.0, empty lists, and None are all falsy.

    From lesson: Type Conversion and Casting

  28. What will print(int('101', 2)) output?

    • 101
    • 5
    • 1
    • Error

    Answer: 5. The int() function accepts a second argument for base. Converting the binary string '101' to base-10 results in 5 (1*4 + 0*2 + 1*1).

    From lesson: Type Conversion and Casting

  29. If x = '10' and y = 5, what is the output of print(x * y)?

    • 50
    • 1010101010
    • Error
    • 105

    Answer: 1010101010. Multiplying a string by an integer performs string repetition. '10' repeated 5 times is '1010101010'. It does not convert the string to an integer.

    From lesson: Type Conversion and Casting

  30. Which conversion is guaranteed to never raise a ValueError?

    • int('12.34')
    • float('abc')
    • str(123)
    • int('1,000')

    Answer: str(123). Converting any object to a string using str() is always safe. The other options involve parsing malformed numeric strings, which triggers a ValueError.

    From lesson: Type Conversion and Casting

  31. Which of the following is the primary purpose of a Python docstring compared to a standard comment?

    • To provide a way to comment out large blocks of code during debugging.
    • To provide documentation that can be accessed programmatically via the __doc__ attribute.
    • To tell the Python interpreter to ignore specific lines during execution.
    • To define metadata that is used exclusively by the compiler.

    Answer: To provide documentation that can be accessed programmatically via the __doc__ attribute.. Docstrings are stored in the __doc__ attribute, allowing tools like help() to retrieve documentation at runtime. Using it to comment out code is incorrect (that is a common misuse), the interpreter doesn't 'ignore' docstrings (they are string objects), and Python is an interpreted language, not a compiled one.

    From lesson: Comments and Docstrings

  32. Where must a docstring be placed to be correctly recognized as the documentation for a function?

    • Immediately before the 'def' keyword of the function.
    • At the very end of the function body.
    • Immediately after the function signature, indented inside the function body.
    • In a separate file named docstring.py.

    Answer: Immediately after the function signature, indented inside the function body.. Python requires the docstring to be the first statement in the function body to associate it with that function. Placing it before 'def' makes it a string literal unrelated to the function scope, and placing it at the end is not the standard convention. A separate file is not how Python documentation is handled.

    From lesson: Comments and Docstrings

  33. When should you use a '#' comment instead of a docstring?

    • To explain the purpose of a class.
    • To document the return type of a function.
    • To explain a complex or 'tricky' segment of code inside a function.
    • To describe the parameters accepted by a function.

    Answer: To explain a complex or 'tricky' segment of code inside a function.. Comments (#) are intended for explaining implementation details, such as tricky logic or algorithm choices. Docstrings are specifically for describing the interface (purpose, arguments, returns) of modules, classes, and functions.

    From lesson: Comments and Docstrings

  34. What happens if you use triple-quoted strings inside a function body but not as the first statement?

    • It becomes a syntax error.
    • The Python interpreter ignores it completely.
    • It is treated as a regular string literal and does not get assigned to the __doc__ attribute.
    • It is automatically treated as a docstring.

    Answer: It is treated as a regular string literal and does not get assigned to the __doc__ attribute.. Only the first string literal in a function body is assigned to __doc__. Any subsequent string literals, even if triple-quoted, are treated as standard expression statements and are ignored by the interpreter, but they are not 'docstrings' in the functional sense.

    From lesson: Comments and Docstrings

  35. Which statement best describes the best practice for writing comments in Python?

    • Comment every line to ensure the code is readable by beginners.
    • Write comments to explain why the code is written a certain way, rather than what the code is doing.
    • Avoid comments entirely, as code should be self-documenting and comments are always redundant.
    • Always use multi-line comments for every function to make the code look professional.

    Answer: Write comments to explain why the code is written a certain way, rather than what the code is doing.. Explaining the 'why' adds value by providing context that the code cannot express. Commenting every line is noisy and redundant, avoiding all comments ignores cases where context is necessary, and overusing multi-line comments for simple tasks is considered poor style.

    From lesson: Comments and Docstrings

  36. Which of the following describes the execution flow if you have an 'if', 'elif', and 'else' chain?

    • All blocks that evaluate to True will execute.
    • Only the first block that evaluates to True will execute, and the rest are skipped.
    • The 'else' block always executes after the 'if' block.
    • Only the last block in the chain will execute.

    Answer: Only the first block that evaluates to True will execute, and the rest are skipped.. In an if-elif-else chain, Python checks conditions in order; once a True condition is found, its block runs and the entire chain is exited. Option 0 is wrong because 'elif' prevents subsequent checks; option 2 is wrong because 'else' only runs if no prior conditions are met; option 3 is wrong because execution is dependent on condition evaluation.

    From lesson: if / elif / else Statements

  37. What is the output of this code snippet: x = 10; if x > 5: print('A'); if x > 8: print('B');

    • A
    • B
    • A and B
    • Nothing

    Answer: A and B. Because these are two separate 'if' statements rather than an 'if-elif', both conditions are checked independently. Both 10 > 5 and 10 > 8 are True. Option 0 and 1 are incomplete, and option 3 is incorrect as both conditions are met.

    From lesson: if / elif / else Statements

  38. Consider: 'if x = 5:'. What happens when Python tries to execute this?

    • It checks if x is equal to 5.
    • It sets x to 5 and proceeds.
    • It raises a SyntaxError.
    • It prints 'True'.

    Answer: It raises a SyntaxError.. The assignment operator '=' is not a valid expression inside an 'if' statement condition. Python expects a boolean expression or a truthy value, and an assignment statement is syntactically invalid here, resulting in a SyntaxError. The others are incorrect because '=' cannot be used for comparison.

    From lesson: if / elif / else Statements

  39. If you want to check if a variable 'score' is between 70 and 80 inclusive, which is the idiomatic Python way?

    • if 70 < score < 80:
    • if score > 70 and score < 80:
    • if 70 <= score <= 80:
    • if score >= 70 or score <= 80:

    Answer: if 70 <= score <= 80:. Python allows chained comparisons. '70 <= score <= 80' correctly includes both endpoints (inclusive). Option 0 excludes endpoints, option 1 excludes endpoints, and option 3 uses 'or', which would result in True for almost any number, making it logically incorrect.

    From lesson: if / elif / else Statements

  40. What happens if all conditions in an if-elif chain are False and there is no 'else' statement?

    • The program crashes.
    • Python executes the last 'elif' by default.
    • The program moves to the next block of code after the chain without executing any branch.
    • The code throws a NameError.

    Answer: The program moves to the next block of code after the chain without executing any branch.. The 'else' block is optional. If no 'if' or 'elif' conditions evaluate to True, the entire structure is skipped, and execution continues with the next line of code. It does not crash or default to another block, nor does it raise a NameError.

    From lesson: if / elif / else Statements

  41. What is the output of: for i in range(1, 4): print(i, end='')

    • 1234
    • 123
    • 0123
    • Error

    Answer: 123. Range(1, 4) starts at 1 and goes up to, but does not include, 4. 1234 includes the endpoint, 0123 includes the start index 0, and Error is incorrect as range is valid.

    From lesson: for Loops

  42. If you have 'my_list = [10, 20, 30]', what is the best way to iterate over both indices and values?

    • for i in range(len(my_list)): print(i, my_list[i])
    • for i, val in enumerate(my_list): print(i, val)
    • for i in my_list: print(i)
    • for i in list(my_list): print(i)

    Answer: for i, val in enumerate(my_list): print(i, val). Enumerate is the standard Pythonic way to get both index and value. Using range(len()) is valid but considered unpythonic, while the others only provide values, not indices.

    From lesson: for Loops

  43. What happens if you use 'break' inside a nested for loop?

    • It terminates both the inner and outer loops.
    • It stops the program entirely.
    • It terminates only the innermost loop where it is called.
    • It causes a syntax error.

    Answer: It terminates only the innermost loop where it is called.. A break statement only exits the current block of the nearest enclosing loop. It does not stop the outer loop, terminate the program, or cause a syntax error.

    From lesson: for Loops

  44. What is the result of 'for i in 'Py': print(i)'?

    • Py
    • P y
    • Error because strings are not iterable
    • Nothing happens

    Answer: P y. Strings are iterable in Python, so the loop iterates character by character. 'print' defaults to adding a newline, so P and y are printed on separate lines. 'Py' would require 'end='''.

    From lesson: for Loops

  45. Which of the following will iterate over the list in reverse order?

    • for i in my_list.reverse():
    • for i in reversed(my_list):
    • for i in my_list[::-1]:
    • Both the second and third options are correct.

    Answer: Both the second and third options are correct.. Both 'reversed()' and slicing '[::-1]' work correctly for iteration. '.reverse()' modifies the list in place and returns None, making it invalid for a 'for' loop expression.

    From lesson: for Loops

  46. What is the output of this code? x = 0; while x < 3: print(x); x += 1

    • 0 1 2 3
    • 0 1 2
    • 1 2 3
    • Infinite loop

    Answer: 0 1 2. The loop runs while x is less than 3. It prints 0, 1, and 2, then stops when x becomes 3. Option 0 includes 3, which is false; option 2 starts at 1, skipping 0; option 3 is false because x is incremented.

    From lesson: while Loops

  47. Which scenario is the primary use case for a while loop in Python?

    • Iterating through each character in a string
    • Repeating an action a specific number of times known beforehand
    • Running code until a specific condition changes regardless of iterations
    • Performing a calculation on every element of a list

    Answer: Running code until a specific condition changes regardless of iterations. While loops are condition-controlled, making them ideal for indefinite loops. For loops are better for sequences and known iteration counts.

    From lesson: while Loops

  48. If you have a while loop with a condition 'while True:', how can you stop the loop?

    • Set the condition to False inside the loop body
    • Use a break statement inside an if block
    • Change the loop to a for loop
    • Simply return from the function containing the loop

    Answer: Use a break statement inside an if block. A 'while True' loop is infinite unless interrupted by 'break' or returning. Setting the condition to False inside the body doesn't work because the 'while' header only checks the condition at the start of the next cycle.

    From lesson: while Loops

  49. What happens if the condition in a while loop is False from the very beginning?

    • The code inside the loop executes once
    • The program raises a SyntaxError
    • The code inside the loop is skipped entirely
    • The program enters an infinite loop

    Answer: The code inside the loop is skipped entirely. A while loop checks its condition before entering the body. If the condition is false initially, the body is never reached. It does not error or loop.

    From lesson: while Loops

  50. What is the effect of the 'continue' statement inside a while loop?

    • It terminates the entire loop immediately
    • It skips the remainder of the current iteration and jumps to the condition check
    • It restarts the loop from the very first iteration
    • It increments the control variable automatically

    Answer: It skips the remainder of the current iteration and jumps to the condition check. The 'continue' statement stops the current iteration and jumps back to the loop header to re-evaluate the condition. Option 0 describes 'break', and the others are incorrect logic for 'continue'.

    From lesson: while Loops

  51. What is the result of running a 'break' statement inside a nested loop?

    • The entire script terminates immediately.
    • Only the innermost loop containing the 'break' is terminated.
    • Both the inner and outer loops are terminated simultaneously.
    • The loop continues from the next iteration of the outer loop.

    Answer: Only the innermost loop containing the 'break' is terminated.. In Python, 'break' exits the loop it is currently in. Option 1 is wrong because only 'sys.exit()' stops the script. Option 3 is wrong because 'break' does not affect outer loops. Option 4 is wrong because 'break' terminates the loop entirely, not just the current iteration.

    From lesson: break, continue, and pass

  52. What happens when 'continue' is executed inside a 'for' loop?

    • The loop is terminated and execution moves to the next block.
    • The code execution pauses until the user provides input.
    • The remaining code inside the loop for the current iteration is skipped, and the loop proceeds to the next item.
    • The program crashes because 'continue' is a reserved keyword.

    Answer: The remaining code inside the loop for the current iteration is skipped, and the loop proceeds to the next item.. The 'continue' statement skips the remainder of the current loop iteration and proceeds to the next iteration. Option 1 describes 'break'. Option 2 describes an input call. Option 4 is false as it is a valid keyword.

    From lesson: break, continue, and pass

  53. When is the 'pass' statement most appropriately used in Python?

    • To terminate a loop prematurely.
    • To skip the current iteration of a loop.
    • To act as a placeholder where syntactically a statement is required but no code is needed.
    • To exit from a function and return a value of None.

    Answer: To act as a placeholder where syntactically a statement is required but no code is needed.. 'pass' is a null operation; it is used when a statement is required by syntax (like in an empty function or loop). Option 1 describes 'break'. Option 2 describes 'continue'. Option 4 describes 'return'.

    From lesson: break, continue, and pass

  54. Consider a 'while' loop with a 'continue' statement. What determines if the loop runs again?

    • The 'continue' statement itself triggers the next iteration regardless of the condition.
    • The loop condition is re-evaluated immediately after the 'continue' statement.
    • The loop terminates if a 'continue' is used.
    • The loop only runs again if a 'pass' statement follows.

    Answer: The loop condition is re-evaluated immediately after the 'continue' statement.. After 'continue', execution jumps back to the top of the loop to re-check the condition. Option 1 is wrong because the condition must still be true. Option 3 describes 'break'. Option 4 is irrelevant to loop control.

    From lesson: break, continue, and pass

  55. Which of the following will cause a SyntaxError in Python?

    • Using 'pass' inside an empty 'if' block.
    • Using 'continue' inside a 'while' loop.
    • Using 'break' inside an 'if' statement that is inside a 'for' loop.
    • Using 'break' outside of any loop structure.

    Answer: Using 'break' outside of any loop structure.. A 'break' statement must be inside a loop; if it is outside, Python raises a SyntaxError. Option 1 is correct syntax for placeholders. Option 2 is valid use. Option 3 is valid because the 'if' is inside a loop, so the 'break' context is correct.

    From lesson: break, continue, and pass

  56. What is the output of the following code: for i in range(2): for j in range(2): print(i, j)?

    • 0 0, 0 1, 1 0, 1 1 on separate lines
    • 0 0, 1 1 on separate lines
    • 0 0 1 1 on one line
    • 0 1, 0 1 on separate lines

    Answer: 0 0, 0 1, 1 0, 1 1 on separate lines. The outer loop runs twice (i=0, then i=1). For each i, the inner loop runs twice (j=0, then j=1). This prints 4 pairs. Other options fail to account for the nested iteration cycle.

    From lesson: Nested Loops and Patterns

  57. To print a right-angled triangle of stars with 'n' rows, what should the inner loop range be?

    • range(n)
    • range(i)
    • range(i + 1)
    • range(1, n)

    Answer: range(i + 1). To get 1 star in row 0, 2 in row 1, etc., the inner loop must iterate 'i+1' times. Using 'range(n)' makes a square; 'range(i)' misses the first star; 'range(1, n)' results in incorrect counts.

    From lesson: Nested Loops and Patterns

  58. If you want to create a pattern that prints a 5x5 grid of numbers starting from 1 to 25, which logic is correct?

    • print(i * 5 + j + 1, end=' ')
    • print(i + j + 1, end=' ')
    • print(i * j + 1, end=' ')
    • print(i + 5 * j + 1, end=' ')

    Answer: print(i * 5 + j + 1, end=' '). i*5 handles the row offset, and j+1 handles the column increment, resulting in a sequential grid. The others result in arithmetic patterns that do not produce a simple 1-25 sequence.

    From lesson: Nested Loops and Patterns

  59. How do you prevent a print statement in a nested loop from moving to the next line prematurely?

    • Use print(value, newline=False)
    • Use print(value, end=" ")
    • Use print(value, stop=" ")
    • Use print(value, sep=" ")

    Answer: Use print(value, end=" "). The 'end' parameter in Python's print function defaults to '\n', so setting it to a space or empty string keeps the cursor on the same line. The other arguments provided are either incorrect or do not control the newline behavior.

    From lesson: Nested Loops and Patterns

  60. What happens if you have an empty print() statement inside the outer loop but outside the inner loop?

    • It stops the program execution
    • It prints an extra space between every row
    • It moves to the next line after the inner loop completes its iteration
    • It prints a blank line before the pattern starts

    Answer: It moves to the next line after the inner loop completes its iteration. An empty print() call defaults to printing a newline character. Placing it after the inner loop finishes ensures that each row of the pattern appears on its own line. The other options describe effects that do not align with standard Python print behavior.

    From lesson: Nested Loops and Patterns

  61. What is the output of list(range(2, 8, 2))?

    • [2, 4, 6, 8]
    • [2, 4, 6]
    • [2, 3, 4, 5, 6, 7]
    • [2, 5, 8]

    Answer: [2, 4, 6]. The correct answer is [2, 4, 6]. range starts at 2, stops before 8, and steps by 2. Option 0 is wrong because it includes 8, which is the exclusive stop limit. Option 2 ignores the step of 2. Option 3 is mathematically incorrect based on the step logic.

    From lesson: The range() Function

  62. Which of the following would produce the sequence [10, 8, 6]?

    • range(10, 6, -2)
    • range(10, 5, -2)
    • range(10, 7, -2)
    • range(10, 4, -2)

    Answer: range(10, 5, -2). range(10, 5, -2) is correct because it starts at 10, moves by -2, and stops before 5 (stopping at 6). Option 0 stops at 8 (not including it). Option 2 stops at 8 (not including it). Option 3 produces [10, 8, 6, 4].

    From lesson: The range() Function

  63. What happens if you run print(range(5, 2))?

    • It prints [5, 4, 3, 2]
    • It prints [5, 4, 3]
    • It prints range(5, 2)
    • It raises a ValueError

    Answer: It prints range(5, 2). In Python 3, range() returns an object, so printing it directly displays the range signature. Options 0 and 1 are wrong because range returns the object, not a list. Option 3 is wrong because the range is empty (start > stop with default positive step).

    From lesson: The range() Function

  64. Why does range(0, 10, 0) raise a ValueError?

    • Because the stop value is larger than the start value
    • Because range() does not support 0 as an argument
    • Because a step of zero would result in an infinite loop
    • Because the step must be a prime number

    Answer: Because a step of zero would result in an infinite loop. The step argument cannot be zero because it would cause an infinite repetition of the same value. Option 0 is incorrect as range() allows ascending sequences. Option 1 is false since 0 can be a start or stop. Option 3 is unrelated to the logic of the function.

    From lesson: The range() Function

  65. What is the result of len(range(0, 100, 5))?

    • 20
    • 100
    • 19
    • 21

    Answer: 20. The range contains values 0, 5, 10... up to 95. This is 20 items. 100/5 = 20. Option 1 is the stop value, not the count. Option 2 and 3 are simple counting errors based on inclusive/exclusive off-by-one mistakes.

    From lesson: The range() Function

  66. What is the output of the following code? def add(a, b): return a + b result = add(5, 3) print(result)

    • 5
    • 3
    • 8
    • None

    Answer: 8. The function correctly takes two arguments, adds them, and returns the integer sum. 5 and 3 are incorrect as they are just the inputs; None is incorrect because the return statement exists.

    From lesson: Defining and Calling Functions

  67. Which of the following describes why we use parameters in function definitions?

    • To make the function execute faster.
    • To allow the function to operate on different data inputs each time it is called.
    • To define global variables that can be used everywhere.
    • To prevent the function from needing a return statement.

    Answer: To allow the function to operate on different data inputs each time it is called.. Parameters provide a way to pass data into a function, making it reusable. Speed is unaffected, parameters do not create global variables, and they do not affect the necessity of return statements.

    From lesson: Defining and Calling Functions

  68. Consider this function: def update(x): x = x + 1 return x val = 10 update(val) print(val) What is printed?

    • 10
    • 11
    • None
    • An error occurs

    Answer: 10. Integers are immutable in Python; the function creates a new local value but does not modify the 'val' variable in the global scope. 11 is wrong because the result was not reassigned; the others are incorrect as no error occurs and it prints the original value.

    From lesson: Defining and Calling Functions

  69. What happens if you define a function with a default parameter like 'def greet(name="User")' but call it as 'greet("Alice")'?

    • The function throws a SyntaxError.
    • The function uses "User" as the name.
    • The function uses "Alice" as the name.
    • The function ignores the argument and prints nothing.

    Answer: The function uses "Alice" as the name.. Positional arguments passed during a call override the default parameter value. SyntaxError and ignoring the argument are incorrect as the code is valid; "User" is incorrect because the default is only used if no argument is provided.

    From lesson: Defining and Calling Functions

  70. What is the result of calling a function that has no 'return' statement?

    • The program crashes.
    • It returns 0.
    • It returns False.
    • It returns None.

    Answer: It returns None.. In Python, functions implicitly return None if no return statement is executed. The program does not crash, nor does it return 0 or False by default.

    From lesson: Defining and Calling Functions

  71. What is the result of calling a function with a mutable default argument, such as 'def add_item(item, list=[]): list.append(item); return list', multiple times?

    • The list is reset to empty every time the function is called.
    • The function returns a new list containing only the passed item each time.
    • The list persists across calls because default arguments are evaluated at definition time.
    • Python raises a syntax error because default arguments cannot be lists.

    Answer: The list persists across calls because default arguments are evaluated at definition time.. The correct answer is the third option because default values in Python are created when the function is defined, not when it is called. Option 1 and 2 are incorrect because the list object is shared. Option 4 is incorrect because Python does not prevent using mutable objects as defaults, even if it is a common logic trap.

    From lesson: Function Arguments and Parameters

  72. Which of the following describes the behavior of passing an integer to a function and incrementing it inside?

    • The integer value in the calling scope is updated automatically.
    • The integer is immutable, so the function creates a new local integer, leaving the original unchanged.
    • The function will raise a TypeError because integers cannot be passed as arguments.
    • The integer becomes a global variable within the function scope.

    Answer: The integer is immutable, so the function creates a new local integer, leaving the original unchanged.. Integers are immutable in Python, so attempting to change them creates a new local reference; the caller's value remains unchanged. Option 1 is wrong because integers are not passed by reference. Option 3 is wrong because integers are valid arguments. Option 4 is wrong because scope rules do not work that way.

    From lesson: Function Arguments and Parameters

  73. What happens if you provide a keyword argument before a positional argument in a function call?

    • Python automatically reorders them to match the function signature.
    • The function ignores the keyword and uses the value positionally.
    • A SyntaxError is raised because positional arguments must follow the order of the definition.
    • The keyword argument takes precedence and is evaluated first.

    Answer: A SyntaxError is raised because positional arguments must follow the order of the definition.. Python strictly enforces that positional arguments appear before keyword arguments. Option 1 is false because Python does not guess intent. Option 2 is false because it violates syntax rules. Option 4 is false because keyword precedence does not override positional requirement rules.

    From lesson: Function Arguments and Parameters

  74. What does the **kwargs syntax allow a function to do?

    • Accept an arbitrary number of positional arguments as a tuple.
    • Accept an arbitrary number of keyword arguments as a dictionary.
    • Force all arguments passed to the function to be converted to strings.
    • Define default values for all arguments in the function signature.

    Answer: Accept an arbitrary number of keyword arguments as a dictionary.. **kwargs captures excess named arguments into a dictionary. Option 1 describes *args. Option 3 is incorrect because type conversion is not the purpose. Option 4 is incorrect because it relates to standard default argument syntax.

    From lesson: Function Arguments and Parameters

  75. If a function is defined as 'def func(a, b=2, *c):', which of the following calls is valid?

    • func(1, 3, 4, 5)
    • func(1, b=3, 4)
    • func(a=1, 2)
    • func(1, 2, c=(4, 5))

    Answer: func(1, 3, 4, 5). Option 0 is correct because 'a' takes 1, 'b' takes 3, and 'c' captures the remaining positional arguments as a tuple (4, 5). Option 1 and 2 are invalid because positional arguments follow keyword arguments. Option 3 is invalid because 'c' is an *args parameter and cannot be assigned by keyword.

    From lesson: Function Arguments and Parameters

  76. What is the output of this code? def append_to(element, to=[]): to.append(element) return to print(append_to(1)) print(append_to(2))

    • [1], [2]
    • [1], [1, 2]
    • [1], [2, 1]
    • Error: Invalid default argument

    Answer: [1], [1, 2]. The list is created at definition time. Option 1 is wrong because it assumes the list is created per call. Option 2 is correct because the same list is mutated. Option 3 assumes order reversal. Option 4 is wrong because mutable defaults are allowed, just discouraged.

    From lesson: Default and Keyword Arguments

  77. Which of the following function definitions is syntactically invalid?

    • def func(a, b=10): pass
    • def func(a=5, b=10): pass
    • def func(a=5, b): pass
    • def func(a, b=None): pass

    Answer: def func(a=5, b): pass. In Python, mandatory positional arguments cannot follow arguments with default values. Option 1, 2, and 4 are valid because they respect the positional-then-default order.

    From lesson: Default and Keyword Arguments

  78. How can you safely use a list as a default argument to ensure a new list is created each time?

    • def func(data=list()): pass
    • def func(data=None): data = data or []
    • def func(data=[]): data = list(data)
    • def func(data=None): if data is None: data = []

    Answer: def func(data=None): if data is None: data = []. Option 3 is the standard idiomatic way to handle mutable defaults safely. Option 1 uses a function call at definition, which doesn't solve the state persistence. Option 2 behaves differently if data is an empty list (evaluates to false). Option 4 correctly checks for None.

    From lesson: Default and Keyword Arguments

  79. Given `def func(a, b=2):`, which of these is an INCORRECT way to call the function?

    • func(1, 3)
    • func(a=1, b=3)
    • func(1, b=3)
    • func(b=3, 1)

    Answer: func(b=3, 1). Python requires positional arguments to come before keyword arguments. Option 3 is incorrect because '1' is positional and comes after 'b=3', which is a keyword argument.

    From lesson: Default and Keyword Arguments

  80. When are default argument values evaluated in Python?

    • Every time the function is called
    • Only once, when the function definition is executed
    • When the script finishes execution
    • Only if the argument is omitted by the caller

    Answer: Only once, when the function definition is executed. Default arguments are bound at the moment the 'def' statement is executed. Option 1 is the most common misconception. Option 3 is irrelevant. Option 4 describes when they are used, not when they are evaluated.

    From lesson: Default and Keyword Arguments

  81. What is the result of calling a function that performs a calculation but has no 'return' statement?

    • It raises a NameError
    • It returns None
    • It returns the last variable defined in the function
    • It returns an empty tuple

    Answer: It returns None. Functions without an explicit return statement return the 'None' object by default. It does not guess the last variable, nor does it error or return a tuple.

    From lesson: Return Values and Multiple Returns

  82. Given 'def get_coords(): return 10, 20', what is the data type of the result if you call 'x = get_coords()'?

    • int
    • list
    • tuple
    • dict

    Answer: tuple. Python automatically packs multiple return values separated by commas into a single tuple. It does not return them as separate integers or a list.

    From lesson: Return Values and Multiple Returns

  83. How do you correctly capture two values returned by a function called 'calculate()'?

    • a, b = calculate()
    • a = calculate(), b = calculate()
    • list(a, b) = calculate()
    • tuple a, b = calculate()

    Answer: a, b = calculate(). Multiple assignment (unpacking) is the standard Python way to assign individual tuple elements returned by a function to separate variables.

    From lesson: Return Values and Multiple Returns

  84. What happens if you have code after a 'return' statement inside the same function block?

    • It executes only if the return condition is true
    • It results in a SyntaxError
    • It is ignored and never executed
    • It is executed before the function exits

    Answer: It is ignored and never executed. A return statement immediately terminates function execution. Any code following it within the same block is unreachable dead code.

    From lesson: Return Values and Multiple Returns

  85. Which of the following is true about returning values in Python?

    • You can only return one object at a time
    • You can return multiple objects which are packed into a tuple
    • You must define the return type in the function header
    • Returning a list is faster than returning a tuple

    Answer: You can return multiple objects which are packed into a tuple. Python allows returning multiple items by packing them into a tuple. You are not limited to one object, no type definition is required, and there is no performance-based requirement to prefer lists over tuples for returns.

    From lesson: Return Values and Multiple Returns

  86. What is the output of the following code? `nums = [1, 2, 3, 4]; result = list(map(lambda x: x * 2, nums))`

    • [1, 2, 3, 4]
    • [2, 4, 6, 8]
    • [1, 4, 9, 16]
    • [0, 2, 4, 6]

    Answer: [2, 4, 6, 8]. The correct answer is `[2, 4, 6, 8]` because the lambda function multiplies each element in `nums` by 2. The other options are incorrect: the first option doesn’t modify the list, the third squares the elements, and the fourth subtracts 1 before multiplying.

    From lesson: Lambda Functions

  87. Which of the following is a valid use of a lambda function?

    • Sorting a list of tuples by the second element: `sorted(tuples, key=lambda x: x[1])`
    • Defining a recursive function: `factorial = lambda n: n * factorial(n-1)`
    • Creating a function with multiple statements: `lambda x: print(x); return x * 2`
    • Using a lambda as a default argument in a function: `def foo(x=lambda: 5): return x()`

    Answer: Sorting a list of tuples by the second element: `sorted(tuples, key=lambda x: x[1])`. The correct answer is sorting a list of tuples by the second element. Lambdas are ideal for simple, one-line operations like this. The other options are incorrect: recursion in lambdas is possible but discouraged, lambdas cannot contain multiple statements, and using lambdas as default arguments can lead to unexpected behavior due to mutable defaults.

    From lesson: Lambda Functions

  88. What does the following code output? `pairs = [(1, 'a'), (2, 'b'), (3, 'c')]; print(sorted(pairs, key=lambda pair: pair[1]))`

    • [(1, 'a'), (2, 'b'), (3, 'c')]
    • [(3, 'c'), (2, 'b'), (1, 'a')]
    • [('a', 1), ('b', 2), ('c', 3)]
    • [(1, 'a'), (3, 'c'), (2, 'b')]

    Answer: [(1, 'a'), (2, 'b'), (3, 'c')]. The correct answer is `[(1, 'a'), (2, 'b'), (3, 'c')]` because the lambda sorts the tuples by the second element (the string), and the strings are already in alphabetical order. The other options are incorrect: the second option reverses the order, the third swaps the tuple elements, and the fourth sorts incorrectly.

    From lesson: Lambda Functions

  89. Why might the following code raise an error? `add = lambda x, y: x + y; print(add(5))`

    • Lambda functions cannot take more than one argument.
    • The lambda is missing a default value for `y`.
    • The lambda is called with too few arguments.
    • Lambda functions cannot be assigned to variables.

    Answer: The lambda is called with too few arguments.. The correct answer is that the lambda is called with too few arguments. The lambda expects two arguments (`x` and `y`), but only one is provided. The other options are incorrect: lambdas can take multiple arguments, default values are allowed but not required, and lambdas can be assigned to variables.

    From lesson: Lambda Functions

  90. Which of the following is NOT a good use case for a lambda function?

    • As a key function for sorting: `sorted(data, key=lambda x: x['age'])`
    • For a simple mathematical operation: `square = lambda x: x ** 2`
    • To define a function with a docstring: `func = lambda x: "This squares x" or x ** 2`
    • In a `filter` operation: `list(filter(lambda x: x % 2 == 0, numbers))`

    Answer: To define a function with a docstring: `func = lambda x: "This squares x" or x ** 2`. The correct answer is defining a function with a docstring. Lambdas cannot have docstrings, and the example provided is syntactically incorrect. The other options are valid use cases: lambdas work well for sorting keys, simple operations, and filtering.

    From lesson: Lambda Functions

  91. What happens if a recursive function in Python fails to reach a base case?

    • The program enters an infinite loop and crashes the computer
    • Python automatically switches to an iterative approach
    • A RecursionError is raised once the maximum recursion depth is exceeded
    • The function returns None by default

    Answer: A RecursionError is raised once the maximum recursion depth is exceeded. Python limits recursion depth to prevent stack overflows; exceeding it triggers a RecursionError. It does not auto-switch to iterative, nor does it return None, nor does it crash the physical hardware.

    From lesson: Recursion

  92. Which of the following describes the purpose of a base case in recursion?

    • To initialize the variables used in the function
    • To provide a stopping condition to prevent infinite calls
    • To increase the speed of the recursive calculations
    • To manage the memory allocation for the call stack

    Answer: To provide a stopping condition to prevent infinite calls. The base case provides a direct result for the simplest input, halting further recursion. It is not for initialization, performance tuning, or low-level memory management.

    From lesson: Recursion

  93. If a function `find_sum(n)` calculates the sum of integers up to n, why is `return find_sum(n-1)` inside the function incorrect?

    • Because it is missing the addition operation and the base case
    • Because Python requires an 'else' block for all recursive calls
    • Because it will cause a syntax error
    • Because 'n' must be a global variable

    Answer: Because it is missing the addition operation and the base case. To return the sum, you must add 'n' to the result of the recursive call (n + find_sum(n-1)) and include a base case (e.g., if n == 0 return 0). The other options describe non-existent requirements or errors.

    From lesson: Recursion

  94. Consider a function that traverses a nested list using recursion. Why is it often better than a loop?

    • It uses significantly less memory than a loop
    • It automatically optimizes the list structure
    • It naturally handles structures of unknown or variable depth
    • It allows the use of Python's built-in sum() function

    Answer: It naturally handles structures of unknown or variable depth. Recursion is ideal for tree-like or nested structures because the stack automatically keeps track of the current depth, which is difficult to manage with simple loops. It does not use less memory, optimize structures, or force the use of sum().

    From lesson: Recursion

  95. What is the result of calling a recursive function that returns a value but the intermediate calls do not return the result of the next call?

    • The final return value will be the correct result
    • The function will return None after the first call finishes
    • The program will execute the code but ignore the recursive result
    • The function will loop infinitely

    Answer: The function will return None after the first call finishes. If a recursive call is made but its return value is not captured or returned, the function effectively finishes its execution path without passing the data back up the stack, usually resulting in None. It isn't an infinite loop, nor does it return the correct result.

    From lesson: Recursion

  96. What is the output of this code: x = 10; def func(): x = 5; func(); print(x)?

    • 5
    • 10
    • NameError
    • None

    Answer: 10. The output is 10. The 'x' inside the function is a local variable; it does not affect the global 'x'. 5 is incorrect because the local variable is destroyed after the function returns; NameError is incorrect as the global 'x' is valid; None is incorrect as 'x' is defined.

    From lesson: Scope, Global, and Local Variables

  97. What happens when you run: x = 10; def func(): print(x); x = 5; func()?

    • 10
    • 5
    • UnboundLocalError
    • None

    Answer: UnboundLocalError. This raises an UnboundLocalError because Python detects 'x' is assigned in the function, making it local, but it is printed before the assignment occurs. 10 and 5 are incorrect because the function scope is determined before execution; None is incorrect as there is no return statement.

    From lesson: Scope, Global, and Local Variables

  98. Which statement correctly describes how Python searches for a variable's value?

    • It searches global, then local, then built-in.
    • It searches local, then enclosing, then global, then built-in.
    • It searches built-in, then global, then local.
    • It searches the most recently defined variable anywhere.

    Answer: It searches local, then enclosing, then global, then built-in.. Python follows the LEGB (Local, Enclosing, Global, Built-in) rule. The other options are incorrect because they describe the search order incorrectly, which would lead to shadowed variables or scope errors.

    From lesson: Scope, Global, and Local Variables

  99. If you define a variable 'y' inside a 'for' loop, where can you access 'y' after the loop finishes?

    • Only inside the loop block.
    • Nowhere, it is deleted after the loop.
    • Anywhere in the same function or global scope.
    • Only inside the 'if' statements following the loop.

    Answer: Anywhere in the same function or global scope.. Python does not have block-level scope, so variables defined in loops persist in the enclosing function or global scope. Option 1 and 4 are incorrect because they restrict scope falsely, and 2 is incorrect because the variable remains in memory.

    From lesson: Scope, Global, and Local Variables

  100. How can you modify a global list variable from within a function without using the 'global' keyword?

    • You must use the 'global' keyword.
    • You can use list methods like .append() because they mutate the object.
    • You can define a new list with the same name inside the function.
    • It is impossible to modify a list without the 'global' keyword.

    Answer: You can use list methods like .append() because they mutate the object.. Mutable objects like lists can be modified in-place without the 'global' keyword because you aren't reassigning the variable name to a new object. The other options are wrong because they suggest impossible restrictions or invalid reassignments.

    From lesson: Scope, Global, and Local Variables

  101. What is the result of executing this code: def make_adder(n): return lambda x: x + n; add5 = make_adder(5); print(add5(10))?

    • 10
    • 15
    • 5
    • None

    Answer: 15. The correct answer is 15. The lambda captures 'n' from the outer scope, creating a closure that adds 5 to the input 10. Other options are mathematically incorrect based on the closure's state.

    From lesson: Closures

  102. Why does a standard loop inside a function creating closures often result in all closures using the final value of the loop variable?

    • Python's garbage collector clears the variable too early.
    • The loop variable is not in the scope of the closure.
    • Closures look up the variable name in the outer scope at execution time.
    • The 'nonlocal' keyword is required for every loop iteration.

    Answer: Closures look up the variable name in the outer scope at execution time.. Closures use late binding; they look up the value of the variable in the parent scope when they are finally executed, by which time the loop has finished and the variable holds the last value. This is why the other options are incorrect.

    From lesson: Closures

  103. Which keyword is required to modify an integer variable defined in the enclosing function from within a nested function?

    • global
    • static
    • nonlocal
    • inner

    Answer: nonlocal. The 'nonlocal' keyword tells Python to search the nearest enclosing scope for the variable rather than creating a local one. 'global' references module level, and the others are not valid keywords for this purpose.

    From lesson: Closures

  104. Consider: def outer(): x = 0; def inner(): x += 1; return x; return inner. Why will this raise an UnboundLocalError?

    • Because x is not defined in the global scope.
    • Because x is being read before assignment inside inner, but assignment makes it local.
    • Because the inner function cannot access x at all.
    • Because integers are immutable and cannot be updated in closures.

    Answer: Because x is being read before assignment inside inner, but assignment makes it local.. When you try to increment 'x', Python interprets 'x' as a local variable for 'inner', but it hasn't been defined yet, hence the error. 'global' is irrelevant here, and integers can be modified with 'nonlocal', not just by assignment.

    From lesson: Closures

  105. What happens to the closure's 'environment' (the variables it captures) after the outer function finishes executing?

    • It is immediately garbage collected.
    • It is persisted in a special __closure__ attribute.
    • It is destroyed if the inner function is not returned.
    • It is copied into the global scope.

    Answer: It is persisted in a special __closure__ attribute.. Python keeps the captured variables alive in the __closure__ attribute of the function object. It is not destroyed as long as the reference to the inner function exists, it is not copied to global, and it is not immediately collected.

    From lesson: Closures

  106. What is the result of executing: x = [1, 2]; y = x; y.append(3); print(x)?

    • [1, 2]
    • [1, 2, 3]
    • [1, 2, 3, 3]
    • Error

    Answer: [1, 2, 3]. In Python, lists are mutable objects passed by reference. Assigning y = x makes y point to the same object as x. Modifying y also modifies x. Option 0 is wrong because the list was modified. Option 2 is wrong because 3 is only appended once. Option 3 is wrong because there is no error.

    From lesson: Lists — Creation and Operations

  107. If list_a = [1, 2] and list_b = [3, 4], what does list_a.append(list_b) produce?

    • [1, 2, 3, 4]
    • [1, 2, [3, 4]]
    • [[1, 2], [3, 4]]
    • [3, 4]

    Answer: [1, 2, [3, 4]]. The .append() method adds its argument as a single element. Since list_b is a list, it is added as a nested list. Option 0 is the result of .extend(). Option 2 and 3 are incorrect because they change the structure of list_a in ways that .append() does not.

    From lesson: Lists — Creation and Operations

  108. Given nums = [10, 20, 30, 40], what is the result of nums[1:3] = [5, 6, 7]?

    • [10, 5, 6, 7, 40]
    • [10, 5, 6, 30, 40]
    • [5, 6, 7, 40]
    • Error

    Answer: [10, 5, 6, 7, 40]. Slice assignment replaces the range defined by the slice (index 1 to 2) with the entirety of the assigned iterable. The indices 1 and 2 (values 20 and 30) are removed, and 5, 6, and 7 are inserted. Option 1 is wrong because it doesn't replace the full slice. Options 2 and 3 are incorrect manipulations of the slice.

    From lesson: Lists — Creation and Operations

  109. Which of the following creates a new list containing elements 1, 2, and 3 without modifying the original?

    • my_list.sort()
    • my_list.append([1, 2, 3])
    • my_list + [1, 2, 3]
    • my_list.extend([1, 2, 3])

    Answer: my_list + [1, 2, 3]. The + operator for lists returns a new concatenated list. .sort(), .append(), and .extend() are all in-place methods that modify the original list and return None, making them unsuitable for creating a new list without side effects.

    From lesson: Lists — Creation and Operations

  110. What is the output of: x = [1, 2]; print(x * 2)?

    • [1, 2, 1, 2]
    • [2, 4]
    • [1, 1, 2, 2]
    • Error

    Answer: [1, 2, 1, 2]. When used with a list, the * operator acts as a repetition operator, duplicating the elements of the list. Option 1 is mathematically incorrect for list operations. Option 2 implies an element-wise multiplication which Python lists do not support directly. Option 3 is a misconception of how repetition works.

    From lesson: Lists — Creation and Operations

  111. What is the result of executing: x = [1, 2]; y = x.append(3); print(y)

    • [1, 2, 3]
    • None
    • [1, 2]
    • Error

    Answer: None. The .append() method performs an in-place modification and returns None. The first option is the state of x, not the return value of the method. The third option ignores the action taken, and there is no error here.

    From lesson: List Methods and Slicing

  112. Given my_list = [10, 20, 30, 40], what is the result of my_list[1:3] = [99, 88, 77]?

    • [10, 99, 88, 77, 40]
    • [10, 99, 88, 77, 30, 40]
    • Error
    • [99, 88, 77]

    Answer: [10, 99, 88, 77, 40]. Slice assignment replaces the range [1:3] (indices 1 and 2) with the provided iterable. Since the slice had 2 elements and we provided 3, the list grows in size. Option 1 is correct. Option 2 replaces too much, option 3 is incorrect as this is valid syntax, and option 4 is the right side, not the list.

    From lesson: List Methods and Slicing

  113. What is the output of [1, 2, 3].extend([4, 5])?

    • [1, 2, 3, 4, 5]
    • [1, 2, 3, [4, 5]]
    • None
    • Error

    Answer: None. Similar to .append(), .extend() modifies the list in place and returns None. Option 1 describes the state of the list afterward, not the return value. Option 2 describes what would happen if you used .append().

    From lesson: List Methods and Slicing

  114. If a = [1, 2, 3], what happens when you perform a[1:1] = [5]?

    • The list becomes [1, 5, 2, 3]
    • The list becomes [1, 5, 3]
    • The list becomes [5, 1, 2, 3]
    • Error: index out of bounds

    Answer: The list becomes [1, 5, 2, 3]. Slicing at index 1:1 specifies an empty slice between index 0 and 1. Assigning a value here inserts it without removing any existing elements. Option 1 correctly reflects an insertion at that position.

    From lesson: List Methods and Slicing

  115. Which of the following operations creates a shallow copy of the entire list 'data'?

    • data.copy()
    • data[:]
    • list(data)
    • All of the above

    Answer: All of the above. All three methods (the .copy() method, the full slice operator [:], and the list() constructor) create a new list object with the same elements. Therefore, all are valid ways to create a shallow copy.

    From lesson: List Methods and Slicing

  116. What is the output of 'type((5))' in Python?

    • <class 'tuple'>
    • <class 'int'>
    • <class 'float'>
    • <class 'object'>

    Answer: <class 'int'>. Parentheses are used for mathematical grouping. Without a comma, (5) is just the integer 5. A tuple requires a trailing comma, as in (5,).

    From lesson: Tuples

  117. Which of the following operations will successfully execute on a tuple 't = (1, 2, 3)'?

    • t[0] = 5
    • t.append(4)
    • new_t = t + (4,)
    • t.remove(1)

    Answer: new_t = t + (4,). Tuples are immutable, so assignment, append, and remove are invalid. Adding two tuples with '+' creates a new tuple, which is allowed.

    From lesson: Tuples

  118. Given 'data = (1, [2, 3])', what happens if you execute 'data[1].append(4)'?

    • It raises a TypeError because tuples are immutable.
    • It raises an AttributeError because lists cannot be inside tuples.
    • The list inside the tuple is updated to [2, 3, 4].
    • The tuple is recreated as (1, [2, 3, 4]).

    Answer: The list inside the tuple is updated to [2, 3, 4].. The tuple's immutability only prevents you from changing its references. Since the second element is a reference to a mutable list, you can modify the list object itself.

    From lesson: Tuples

  119. What is the result of 'a, b = (10, 20, 30)'?

    • a=10, b=20
    • a=10, b=(20, 30)
    • It raises a ValueError.
    • a=(10, 20), b=30

    Answer: It raises a ValueError.. Python expects the number of variables to match the number of elements in the tuple. Since there are 3 elements and 2 variables, a ValueError is raised unless a starred expression is used.

    From lesson: Tuples

  120. Why would a developer choose a tuple over a list for a collection of data?

    • To allow for easier sorting using .sort()
    • To provide a read-only collection that cannot be accidentally modified.
    • To enable faster index-based insertion of new elements.
    • To convert the data to a dictionary key more easily.

    Answer: To provide a read-only collection that cannot be accidentally modified.. Tuples are immutable, making them ideal for representing fixed records. Lists are mutable and do not allow being used as dictionary keys, whereas tuples can be used as keys if they contain only immutable items.

    From lesson: Tuples

  121. What is the output of len({1, 2, 2, 3, 3, 3})?

    • 6
    • 3
    • 2
    • Error

    Answer: 3. Sets only store unique elements. The duplicates are removed, leaving {1, 2, 3}, which has a length of 3. Other options ignore the property of uniqueness.

    From lesson: Sets and Set Operations

  122. Which of the following operations is valid for a set in Python?

    • s[0]
    • s.append(4)
    • s.add((1, 2))
    • s.add([1, 2])

    Answer: s.add((1, 2)). Sets are unordered, so indexing (s[0]) is impossible. Append is for lists. You can add a tuple because it is immutable (hashable), but you cannot add a list because it is mutable.

    From lesson: Sets and Set Operations

  123. What is the result of {1, 2, 3} & {3, 4, 5}?

    • {1, 2, 3, 4, 5}
    • {3}
    • {1, 2, 4, 5}
    • None

    Answer: {3}. The & operator represents set intersection. It returns elements present in both sets. {3} is the only common element. {1,2,3,4,5} is a union (|), and {1,2,4,5} is a symmetric difference (^).

    From lesson: Sets and Set Operations

  124. Which statement best describes the difference between set.remove(x) and set.discard(x)?

    • remove() returns the element, discard() does not
    • remove() raises a KeyError if x is missing, discard() does not
    • discard() is faster than remove()
    • remove() is for sets, discard() is for dictionaries

    Answer: remove() raises a KeyError if x is missing, discard() does not. Both remove elements. The critical difference is error handling; remove() throws an exception if the item is not found, while discard() silently does nothing.

    From lesson: Sets and Set Operations

  125. If s = {1, 2, 3}, what happens after s.update([4, 5])?

    • s becomes {1, 2, 3, [4, 5]}
    • s becomes {1, 2, 3, 4, 5}
    • s remains {1, 2, 3}
    • An error is raised

    Answer: s becomes {1, 2, 3, 4, 5}. The update() method takes an iterable and adds all its elements to the set. It merges the contents. The first option would only happen if using add(), but that would fail because a list is not hashable.

    From lesson: Sets and Set Operations

  126. Which of the following is a valid way to create a dictionary in Python?

    • d = {1, 2, 3}
    • d = {'a': 1, 'b': 2}
    • d = (('a', 1), ('b', 2))
    • d = ['a': 1, 'b': 2]

    Answer: d = {'a': 1, 'b': 2}. Option 1 creates a set, not a dictionary. Option 2 uses the correct curly brace syntax with key-value pairs. Option 3 creates a tuple of tuples. Option 4 uses square brackets incorrectly. Dictionaries require key:value syntax within braces.

    From lesson: Dictionaries

  127. What is the result of calling d.get('x', 0) on a dictionary d that does not contain the key 'x'?

    • It raises a KeyError
    • It returns None
    • It returns 0
    • It adds the key 'x' with value 0 to the dictionary

    Answer: It returns 0. The get() method returns the provided default value (0) if the key is not found. It does not raise an error, does not return None if a default is provided, and does not modify the dictionary.

    From lesson: Dictionaries

  128. Why can you not use a list as a dictionary key?

    • Because lists are too large
    • Because lists do not have a length
    • Because lists are mutable and therefore unhashable
    • Because lists are ordered

    Answer: Because lists are mutable and therefore unhashable. Dictionary keys must be immutable so that their hash value remains constant. Since lists can be changed (mutated), they are unhashable. The other options are incorrect as lists have length and ordering.

    From lesson: Dictionaries

  129. What happens when you use the dictionary unpacking operator **d in a function call?

    • It passes the dictionary as a single keyword argument
    • It passes the keys and values as keyword arguments
    • It raises a TypeError
    • It converts the dictionary into a list of tuples

    Answer: It passes the keys and values as keyword arguments. The ** operator unpacks the dictionary into individual keyword arguments, where the key names become the parameter names. It does not pass the dictionary as one object or raise an error.

    From lesson: Dictionaries

  130. What is the most efficient way to check if a specific key exists in a dictionary?

    • Using 'if key in d.keys():'
    • Using 'try-except' block
    • Using 'if key in d:'
    • Using 'if d.get(key) is not None:'

    Answer: Using 'if key in d:'. Using 'if key in d' is the standard, most idiomatic, and efficient way to check for key existence. 'd.keys()' creates an unnecessary view object, 'try-except' is overhead-heavy, and 'get()' check fails if the key exists but its value is None.

    From lesson: Dictionaries

  131. Given data = {'a': 1}, what happens if you execute data.update({'b': 2})?

    • data becomes {'a': 1, 'b': 2} and the expression returns the new dictionary.
    • data remains {'a': 1} and the expression returns {'a': 1, 'b': 2}.
    • data becomes {'a': 1, 'b': 2} and the expression returns None.
    • An error is raised because update only accepts list of tuples.

    Answer: data becomes {'a': 1, 'b': 2} and the expression returns None.. The .update() method modifies dictionaries in-place and returns None. The first and second options are wrong because the return value is not the dictionary itself; the fourth is wrong because update accepts any mapping or iterable of key-value pairs.

    From lesson: Dictionary Methods and Nested Dicts

  132. Which approach safely retrieves a nested value 'x' inside 'y' from dictionary 'd', returning 0 if either is missing?

    • d.get('y', {}).get('x', 0)
    • d.get('y').get('x', 0)
    • d['y']['x'] if 'y' in d else 0
    • d.get('y', {}).get('x') or 0

    Answer: d.get('y', {}).get('x', 0). The first option is safe because it provides a default empty dict if 'y' is missing, then safely checks for 'x'. Option 2 fails if 'y' is missing (AttributeError on None). Option 3 is verbose and prone to errors. Option 4 is risky because if 'x' exists but is 0 or False, the 'or' will return 0 even if the key existed.

    From lesson: Dictionary Methods and Nested Dicts

  133. What is the result of {}.fromkeys(['a', 'b'], [])?

    • {'a': None, 'b': None}
    • {'a': [], 'b': []}
    • A dictionary where both keys point to the exact same list object in memory.
    • An error because values in fromkeys must be immutable.

    Answer: A dictionary where both keys point to the exact same list object in memory.. The value passed to fromkeys is shared by reference among all keys. Option 1 is wrong as it ignores the provided list. Option 2 describes the values but misses the important detail about shared memory. Option 4 is false as values can be any type.

    From lesson: Dictionary Methods and Nested Dicts

  134. Why is it generally discouraged to use dictionary unpacking (d1 | d2) to merge nested dictionaries?

    • It is significantly slower than using a loop.
    • It performs a shallow merge, meaning nested dictionaries in d2 overwrite the reference to nested dictionaries in d1.
    • It only works on dictionaries with integer keys.
    • It raises a TypeError if the dictionaries have common keys.

    Answer: It performs a shallow merge, meaning nested dictionaries in d2 overwrite the reference to nested dictionaries in d1.. The pipe operator creates a shallow copy. If both dictionaries contain a key pointing to a nested dictionary, the second dictionary's nested object replaces the first entirely, rather than merging their contents. The other options are incorrect regarding performance, limitations, or errors.

    From lesson: Dictionary Methods and Nested Dicts

  135. What happens when you call pop() on a dictionary with an empty key list?

    • It removes the last inserted item in Python 3.7+.
    • It raises a KeyError.
    • It returns None.
    • It removes the first item in the dictionary.

    Answer: It raises a KeyError.. The pop() method requires a key argument to function. Without a key, it raises a TypeError. If you provide a key that doesn't exist, it raises a KeyError. None of the other options describe the standard behavior of the method.

    From lesson: Dictionary Methods and Nested Dicts

  136. What is the output of [x**2 for x in range(3) if x % 2 == 0]?

    • [0, 4]
    • [0, 1, 4]
    • [1, 4]
    • [0, 2, 4]

    Answer: [0, 4]. The range(3) produces 0, 1, 2. The filter 'if x % 2 == 0' selects 0 and 2. Squaring these yields 0 and 4. Other options fail because they include odd numbers or calculate incorrect squares.

    From lesson: List Comprehensions

  137. Which of these correctly implements an if-else mapping in a list comprehension?

    • [x if x > 0 for x in range(5) else 0]
    • [x > 0 ? x : 0 for x in range(5)]
    • [x if x > 0 else 0 for x in range(5)]
    • [(x > 0) if x else 0 for x in range(5)]

    Answer: [x if x > 0 else 0 for x in range(5)]. In Python, the ternary operator 'x if condition else y' is placed before the loop. Option 0 has improper syntax, Option 1 uses C-style syntax not valid in Python, and Option 3 applies the logic incorrectly.

    From lesson: List Comprehensions

  138. If you need to flatten a 2D list 'matrix = [[1, 2], [3, 4]]', which comprehension is correct?

    • [item for row in matrix for item in row]
    • [item for item in row for row in matrix]
    • [row for item in row for row in matrix]
    • [matrix for row in matrix for item in row]

    Answer: [item for row in matrix for item in row]. The order of loops follows nested loop syntax: outer loop first, inner loop second. Option 0 correctly iterates rows then items. Other options fail due to incorrect ordering or structure.

    From lesson: List Comprehensions

  139. What happens if you use parentheses instead of square brackets in a comprehension: (x for x in range(3))?

    • It creates a list.
    • It creates a tuple.
    • It creates a generator object.
    • It causes a SyntaxError.

    Answer: It creates a generator object.. Parentheses create a generator expression, which yields items one by one lazily. It is not a list, a tuple, or an error. A tuple comprehension does not exist in this syntax.

    From lesson: List Comprehensions

  140. What is the result of [x for x in 'ABC' if x == 'B' else 'D']?

    • ['D', 'B', 'D']
    • ['A', 'B', 'C']
    • ['D', 'D', 'D']
    • SyntaxError

    Answer: SyntaxError. The syntax is invalid because the 'if-else' must be placed before the 'for' clause when mapping values. You cannot place an 'else' block after a filter condition.

    From lesson: List Comprehensions

  141. What is the result of {x: x % 2 for x in range(3)}?

    • {0: 0, 1: 1, 2: 0}
    • {0, 1, 0}
    • {0: 0, 1: 1, 2: 1}
    • TypeError

    Answer: {0: 0, 1: 1, 2: 0}. The expression creates a dictionary where keys are 0, 1, 2 and values are the remainder of division by 2. Option 1 is incorrect because it is a set literal, not a dict. Option 2 is a partial mapping, and Option 3 calculates the wrong values.

    From lesson: Dict and Set Comprehensions

  142. If you define {x**2 for x in [-2, 2]}, what is the resulting object?

    • [4, 4]
    • {4, 4}
    • {4}
    • Error: duplicate keys

    Answer: {4}. This is a set comprehension. Since sets only store unique elements, {4, 4} simplifies to {4}. Option 0 is a list, and Option 1 is invalid set representation in output. Option 3 is incorrect because sets ignore duplicates rather than raising an error.

    From lesson: Dict and Set Comprehensions

  143. What happens when you run {i: i*2 for i in range(2) for i in range(2)}?

    • {0: 0, 1: 2}
    • {0: 2, 1: 2}
    • NameError
    • SyntaxError

    Answer: {0: 2, 1: 2}. The inner loop runs after the outer loop completes, overwriting the keys. The final state of the dict is determined by the last assignment to each key. Option 0 is wrong because it ignores the overwrite behavior. Options 2 and 3 are wrong as the syntax is valid.

    From lesson: Dict and Set Comprehensions

  144. Which of the following creates a dictionary mapping each character in 'ABA' to its length?

    • {char: len(char) for char in 'ABA'}
    • [char: len(char) for char in 'ABA']
    • {char for char in 'ABA'}
    • {char: len('ABA') for char in 'ABA'}

    Answer: {char: len(char) for char in 'ABA'}. Option 0 correctly maps characters to their individual lengths (which is always 1). Option 1 uses list syntax. Option 2 creates a set. Option 3 maps to the total string length, which is not what was requested.

    From lesson: Dict and Set Comprehensions

  145. What is the outcome of {x for x in range(5) if x > 2 if x < 4}?

    • {3}
    • {2, 3, 4}
    • {3, 4}
    • An empty set

    Answer: {3}. The chained 'if' conditions act like a logical AND. Only 3 satisfies both x > 2 and x < 4. Option 1 and 2 include values outside the range, and Option 3 is wrong because 4 is not less than 4.

    From lesson: Dict and Set Comprehensions

  146. If you need to implement a queue efficiently in Python, why is collections.deque preferred over a standard list?

    • It provides a popleft() method that performs in O(1) time.
    • It uses less memory than a list for storing integers.
    • It automatically enforces the data type of the elements inside.
    • It allows for faster random access to elements in the middle.

    Answer: It provides a popleft() method that performs in O(1) time.. deque is implemented as a doubly linked list, making popleft() O(1). Lists require O(n) for shifting elements. The other options are incorrect as memory usage is similar, type enforcement isn't a feature, and list random access is actually faster.

    From lesson: Stacks and Queues using Python

  147. What is the result of using a Python list as a stack if you only use .append() and .pop()?

    • It behaves as a FIFO structure because the list grows dynamically.
    • It behaves as a LIFO structure with O(1) average time complexity.
    • It triggers a memory error when the list reaches a certain size.
    • It automatically converts into a linked list for efficiency.

    Answer: It behaves as a LIFO structure with O(1) average time complexity.. append() and pop() operate on the end of a list, which are amortized O(1) operations, satisfying LIFO requirements. FIFO is wrong (that's a queue). Memory errors are not standard behavior, and it does not convert to a linked list.

    From lesson: Stacks and Queues using Python

  148. Consider a scenario where you must reverse the order of items using a data structure. Which is the most natural fit?

    • A Queue, because it keeps items in their arrival order.
    • A Stack, because the last item added is the first one removed.
    • A Set, because it automatically sorts elements.
    • A Dictionary, because it maps keys to the reversed indices.

    Answer: A Stack, because the last item added is the first one removed.. A stack follows LIFO, which inherently reverses the sequence of elements pushed onto it. Queues maintain order (FIFO). Sets do not preserve order, and dictionaries are for key-value lookups.

    From lesson: Stacks and Queues using Python

  149. When implementing a queue using two stacks, what is the process for dequeuing an element?

    • Always pop from the first stack until it is empty.
    • If the second stack is empty, pop all elements from the first stack and push them into the second, then pop from the second.
    • Pop the bottom element of the first stack using an index.
    • Move the first stack to a new list and use the pop(0) method.

    Answer: If the second stack is empty, pop all elements from the first stack and push them into the second, then pop from the second.. Moving elements from the first to the second stack reverses the order, effectively transforming LIFO into FIFO. Other options are inefficient, use prohibited list operations, or fail to achieve the required order.

    From lesson: Stacks and Queues using Python

  150. Why might you choose a list over a deque for a stack implementation in Python?

    • Lists are faster for all operations than deques.
    • Lists are specifically optimized for LIFO operations at the end of the container.
    • Lists support thread-safe operations by default while deques do not.
    • Lists require less boilerplate code for simple stack requirements.

    Answer: Lists are specifically optimized for LIFO operations at the end of the container.. Lists are highly optimized for appending and popping from the end, which is exactly what a stack needs. Deques are better for queues (front/back), but lists are perfectly valid for stacks. The other claims regarding performance speed, thread safety, and boilerplate are either false or misleading.

    From lesson: Stacks and Queues using Python

  151. What is the primary purpose of the 'self' parameter in a class method?

    • It refers to the class definition itself.
    • It acts as a reference to the specific instance that called the method.
    • It acts as a placeholder for any external arguments passed to the method.
    • It is used to define static methods that do not need data.

    Answer: It acts as a reference to the specific instance that called the method.. Option 1 is correct because 'self' allows the method to access and modify the specific attributes of the individual object. Option 0 is wrong because the class itself is accessed via the class name. Option 2 is wrong because arguments are passed after self. Option 3 is wrong because static methods do not use 'self'.

    From lesson: Classes and Objects

  152. Given a class 'Car', what happens if you assign a value to a variable directly inside the class body (outside any method)?

    • It creates an instance attribute for every new object.
    • It raises a SyntaxError because all variables must be in methods.
    • It creates a class attribute shared by all instances of the class.
    • It creates a local variable that is deleted after the class is defined.

    Answer: It creates a class attribute shared by all instances of the class.. Option 2 is correct because variables defined directly in the class block are shared across all instances. Option 0 is incorrect because instance attributes must be created using 'self' in a method. Option 1 is incorrect because it is valid syntax. Option 3 is incorrect because the variable persists for the life of the program.

    From lesson: Classes and Objects

  153. What is the behavior of the __init__ method?

    • It is called automatically when a new instance of a class is created.
    • It must be called manually to finalize object setup.
    • It is a static method used to define class constants.
    • It is required to return a value to the caller.

    Answer: It is called automatically when a new instance of a class is created.. Option 0 is correct as __init__ is the constructor method in Python. Option 1 is wrong because Python calls it automatically during instantiation. Option 2 is wrong because it is not static. Option 3 is wrong because the constructor must return None, not a value.

    From lesson: Classes and Objects

  154. When inheriting from a parent class, why is super().__init__() typically used?

    • To allow the child class to rename parent methods.
    • To run the parent's initialization logic to ensure proper object setup.
    • To prevent the child class from having its own attributes.
    • To make the child class methods static.

    Answer: To run the parent's initialization logic to ensure proper object setup.. Option 1 is correct; super().__init__() ensures that attributes initialized by the parent are properly assigned. Option 0 is wrong because super does not rename methods. Option 2 is wrong because it does not prevent child attributes. Option 3 is wrong because super has no effect on method staticity.

    From lesson: Classes and Objects

  155. What is the result of calling a method on an object if that method was defined without the 'self' argument?

    • It works perfectly if there are no other arguments.
    • It works but the object data is inaccessible.
    • It raises a TypeError when called on an instance.
    • It automatically becomes a class method.

    Answer: It raises a TypeError when called on an instance.. Option 2 is correct; Python automatically injects the instance as the first argument, so a method expecting zero arguments will receive one, causing an error. Option 0 and 1 are wrong because the call will always fail. Option 3 is wrong because a method must be decorated with @classmethod to become a class method.

    From lesson: Classes and Objects

  156. What is the primary purpose of the __init__ method in a Python class?

    • To allocate memory for the object
    • To define the initial state of the object
    • To destroy the object when it is deleted
    • To return a new instance of the class

    Answer: To define the initial state of the object. The correct answer is defining the initial state; it assigns values to attributes. It does not allocate memory (handled by __new__), it doesn't destroy objects (that's __del__), and it does not return the instance (it returns None).

    From lesson: The __init__ Constructor

  157. If you define a class without an __init__ method, what happens?

    • Python raises an AttributeError when you instantiate the class
    • The object will have no attributes at all
    • Python uses a default constructor that does nothing
    • You cannot instantiate the class

    Answer: Python uses a default constructor that does nothing. Python provides a default __init__ that performs no operations, allowing the object to be created successfully. It does not prevent instantiation or raise errors.

    From lesson: The __init__ Constructor

  158. Why is the 'self' parameter mandatory in the __init__ method?

    • To refer to the specific instance being initialized
    • To indicate that the method is private
    • To optimize the speed of the constructor
    • To access global variables in the script

    Answer: To refer to the specific instance being initialized. Self represents the instance itself, allowing the constructor to assign values to that specific object. It has nothing to do with privacy, speed, or global scopes.

    From lesson: The __init__ Constructor

  159. What happens if you explicitly add 'return "Success"' inside an __init__ method?

    • The object will be successfully created with that return value
    • A TypeError is raised because constructors must return None
    • The instance will be initialized, but the return value is ignored
    • The class will be returned instead of the instance

    Answer: A TypeError is raised because constructors must return None. Python strictly forbids return values in the constructor because it is intended only for initialization. Returning a value triggers a TypeError.

    From lesson: The __init__ Constructor

  160. Which of the following correctly assigns an attribute to an instance during initialization?

    • self.value = value
    • value = self.value
    • __init__.value = value
    • self:value = value

    Answer: self.value = value. Using 'self.name = value' attaches the data to the object's dictionary. The other options involve incorrect syntax or assignment order that would not persist the value on the instance.

    From lesson: The __init__ Constructor

  161. Given a class 'Data' with a class variable 'count = 0', if you increment it using 'self.count += 1' inside an instance method, what happens?

    • It increments the class variable for all instances globally.
    • It creates a new instance variable 'count' for that specific instance, shadowing the class variable.
    • It raises an AttributeError because class variables cannot be accessed via self.
    • It modifies the class variable only if the instance has no other attributes.

    Answer: It creates a new instance variable 'count' for that specific instance, shadowing the class variable.. Option 2 is correct because the assignment creates a local instance attribute. Option 1 is wrong because assigning to 'self.count' does not update the class attribute. Option 3 is wrong because self can access class attributes. Option 4 is incorrect because attribute lookup is independent of other attributes.

    From lesson: Instance vs Class Variables

  162. What is the primary difference between how Python resolves attributes for 'self.var' and 'ClassName.var'?

    • self.var always checks the class first, then the instance.
    • ClassName.var only exists if an instance of the class has been created.
    • self.var checks the instance scope first, while ClassName.var checks the class scope directly.
    • They are identical and interchangeable in all Python versions.

    Answer: self.var checks the instance scope first, while ClassName.var checks the class scope directly.. Option 3 correctly describes the resolution order. Option 1 is wrong because instance lookup happens before class lookup. Option 2 is false as class variables are defined on the class itself. Option 4 is false as the scopes are distinct.

    From lesson: Instance vs Class Variables

  163. If a class has a list as a class variable, what is the risk of modifying it via an instance?

    • The modification only affects the current instance.
    • The list is copied to the instance scope automatically.
    • All instances of the class will see the modified list since they share the same object reference.
    • The code will throw a TypeError.

    Answer: All instances of the class will see the modified list since they share the same object reference.. Option 3 is correct because mutable objects like lists are shared. Option 1 is wrong because the mutation is in-place on the shared object. Option 2 is wrong because Python does not auto-copy. Option 4 is wrong because modifying an existing list is a valid operation.

    From lesson: Instance vs Class Variables

  164. Why should you typically initialize variables in the __init__ method?

    • To ensure they are treated as class variables.
    • To ensure they are unique to every instance created.
    • Because Python variables cannot be defined outside of methods.
    • To make the variables private.

    Answer: To ensure they are unique to every instance created.. Option 2 is correct because __init__ runs per instance creation. Option 1 is wrong because __init__ defines instance variables. Option 3 is wrong as class body definitions are legal. Option 4 is wrong because __init__ does not control visibility.

    From lesson: Instance vs Class Variables

  165. When is a variable defined in the class body accessible?

    • Only after an instance of the class has been instantiated.
    • Only within instance methods using self.
    • As soon as the class definition is executed, even without an instance.
    • Only when the variable is explicitly declared as global.

    Answer: As soon as the class definition is executed, even without an instance.. Option 3 is correct because class variables are defined when the class is created in memory. Option 1 is wrong because the class exists independently of instances. Option 2 is wrong because they are accessible via the class name. Option 4 is wrong as 'global' has no impact on class attributes.

    From lesson: Instance vs Class Variables

  166. What is the primary role of the 'self' parameter in a Python instance method?

    • It refers to the class definition itself
    • It acts as a reference to the specific object instance the method was called on
    • It is a pointer to the parent class in an inheritance hierarchy
    • It holds the arguments passed to the method during the call

    Answer: It acts as a reference to the specific object instance the method was called on. Option 2 is correct because 'self' provides access to the attributes and other methods of the instance. Option 1 is wrong because the class is accessed via 'cls'. Option 3 is wrong because inheritance is handled by 'super()'. Option 4 is wrong because arguments are passed as separate parameters following 'self'.

    From lesson: Instance Methods

  167. If you have a class 'Robot' with a method 'def greet(self):', how should you invoke this method for an instance 'r1'?

    • Robot.greet()
    • Robot.greet(r1)
    • r1.greet()
    • greet(r1)

    Answer: r1.greet(). Option 3 is correct because the dot notation automatically passes the instance as the first argument. Option 1 is wrong because it lacks the instance context. Option 2 is valid syntax but non-idiomatic, and Option 4 is wrong because 'greet' is not defined in the global scope.

    From lesson: Instance Methods

  168. Why does accessing an attribute inside a method require using 'self.'?

    • To differentiate between local variables and instance-specific state
    • To allow the variable to be accessible by other classes
    • Because Python requires the 'self' namespace for global variables
    • To increase the speed of the lookup process

    Answer: To differentiate between local variables and instance-specific state. Option 1 is correct because local method variables and instance attributes exist in different namespaces. Options 2 and 3 are incorrect regarding Python's scoping rules. Option 4 is wrong; it actually adds a minor lookup step.

    From lesson: Instance Methods

  169. What happens if you define an instance method with zero parameters?

    • It will work fine as long as you don't use attributes
    • It will be interpreted as a static method automatically
    • It will raise a TypeError when called on an instance
    • It will successfully receive the class object as the first parameter

    Answer: It will raise a TypeError when called on an instance. Option 3 is correct because Python mandates that the instance is passed as the first argument, and a zero-parameter method cannot accept it. Option 1 is false because the passing mechanism occurs regardless. Option 2 and 4 describe behavior related to other method types.

    From lesson: Instance Methods

  170. Consider a method defined as 'def update(self, val):'. If you call 'obj.update(10)', what is the value of 'self'?

    • The integer 10
    • The class object 'obj'
    • The memory address of the method 'update'
    • The instance 'obj'

    Answer: The instance 'obj'. Option 4 is correct because 'self' is the placeholder for the instance 'obj'. Option 1 is incorrect because 10 is passed as the 'val' argument. Option 2 confuses the instance with the type. Option 3 is irrelevant to the function's parameter binding.

    From lesson: Instance Methods

  171. What is the primary role of the super() function in Python?

    • To explicitly call a method from a parent class without knowing its name
    • To prevent a subclass from inheriting specific methods
    • To create a new instance of the parent class
    • To force the interpreter to use the base object class

    Answer: To explicitly call a method from a parent class without knowing its name. Option 0 is correct because super() returns a proxy object that delegates method calls to the parent or sibling classes in the MRO. Option 1 is wrong because it facilitates inheritance, not prevents it. Option 2 is wrong because it delegates to an existing parent, not creating a new object. Option 3 is wrong because super() is about method delegation, not base class instantiation.

    From lesson: Inheritance and super()

  172. If you have a class C inheriting from B and A (in that order), what does super() in class C do?

    • It exclusively calls the constructor of class A
    • It calls the next class in the Method Resolution Order (MRO)
    • It calls both class A and class B simultaneously
    • It bypasses all parent classes to call the object class

    Answer: It calls the next class in the Method Resolution Order (MRO). Option 1 is correct because super() follows the MRO, which is computed based on the class hierarchy. Option 0 is wrong because it calls the *next* class, not necessarily the first parent. Option 2 is wrong because Python does not execute multiple parents in parallel. Option 3 is wrong because super() respects the established hierarchy chain.

    From lesson: Inheritance and super()

  173. Why is it dangerous to ignore super().__init__() in a subclass constructor?

    • It will cause a syntax error immediately
    • The parent class's instance attributes will not be initialized
    • Python will automatically stop the program execution
    • It makes the subclass unable to have its own methods

    Answer: The parent class's instance attributes will not be initialized. Option 1 is correct because parent constructors define essential data fields; missing them causes AttributeErrors later. Option 0 is wrong because omitting super() is syntactically valid code. Option 2 is wrong because Python continues execution until an error is triggered. Option 3 is wrong because subclasses can freely define methods regardless of the parent constructor.

    From lesson: Inheritance and super()

  174. In Python 3, how should you correctly call the superclass constructor?

    • super(self, ClassName).__init__()
    • ParentClassName.__init__(self)
    • super().__init__()
    • __init__(super())

    Answer: super().__init__(). Option 2 is the standard Python 3 syntax which automatically detects the class and instance context. Option 0 is invalid syntax for super. Option 1, while technically functional, is the 'old' style and bypasses the MRO logic that super() provides. Option 3 is syntactically incorrect.

    From lesson: Inheritance and super()

  175. How does super() handle multiple inheritance scenarios?

    • It calls the parent class listed furthest to the left
    • It calls all parent classes at the same time
    • It follows the order defined by the Method Resolution Order (MRO)
    • It requires a custom decorator to identify the correct parent

    Answer: It follows the order defined by the Method Resolution Order (MRO). Option 2 is correct because the MRO provides a linear sequence for delegation. Option 0 is wrong because the MRO is calculated by the C3 linearization algorithm, not just order. Option 1 is wrong because Python is single-threaded in its execution flow here. Option 3 is wrong because super() is built-in and does not require extra decorators.

    From lesson: Inheritance and super()

  176. If Class B inherits from Class A, and both define a method 'display()', what happens when you call B().display()?

    • Python executes both methods sequentially.
    • The version in Class B overrides the version in Class A.
    • The version in Class A is called because it is the parent.
    • A NameError is raised because the method is defined twice.

    Answer: The version in Class B overrides the version in Class A.. In Python, methods in a subclass override methods with the same name in the parent class (Method Overriding). Option 0 is wrong because they don't run together automatically. Option 2 is wrong because the subclass method takes precedence. Option 3 is wrong as it is standard behavior.

    From lesson: Method Overriding and Polymorphism

  177. What is the primary benefit of using 'super()' in an overridden method?

    • It forces the program to execute the parent method immediately.
    • It allows the subclass to extend, rather than just replace, the parent's functionality.
    • It prevents the subclass from accessing its own attributes.
    • It is required to make the class compatible with multiple inheritance.

    Answer: It allows the subclass to extend, rather than just replace, the parent's functionality.. super() allows you to trigger the parent's implementation while adding custom logic before or after, thus extending functionality. Option 0 is imprecise. Option 2 is irrelevant. Option 3 is false, as super() is not strictly required for inheritance, only for calling parent code.

    From lesson: Method Overriding and Polymorphism

  178. Given the following: class A: def show(self, x=1): pass; class B(A): def show(self, x): pass. How does this affect polymorphism?

    • Class B is not a valid subclass of A.
    • It breaks polymorphism because B changed the signature of the method.
    • Polymorphism remains intact as long as B can accept the same inputs A expects.
    • It triggers a TypeError at runtime whenever 'show' is called on B.

    Answer: Polymorphism remains intact as long as B can accept the same inputs A expects.. Polymorphism relies on interfaces; if B's 'show' can handle the inputs A's 'show' handles, the program remains robust. Option 0 is false. Option 1 is too strict; changing the signature is acceptable if it respects the contract. Option 3 is incorrect because a TypeError only occurs if the arguments passed don't match the required definition.

    From lesson: Method Overriding and Polymorphism

  179. What does the 'Method Resolution Order' (MRO) determine in Python?

    • The order in which memory is allocated for class attributes.
    • The sequence in which Python searches for a method in a class hierarchy.
    • Which subclass is instantiated first when creating an object.
    • The priority given to methods based on their parameter count.

    Answer: The sequence in which Python searches for a method in a class hierarchy.. MRO defines the order of classes Python searches when looking for a method, especially in multiple inheritance. Options 0, 2, and 3 describe processes unrelated to the MRO's purpose of finding method definitions.

    From lesson: Method Overriding and Polymorphism

  180. How does Python handle 'Method Overloading' (different methods with same name but different arguments)?

    • It requires a decorator to enable explicit overloading.
    • It is handled natively by the interpreter during class definition.
    • It does not support it; the last defined method replaces earlier ones.
    • It requires the use of 'abstract' classes.

    Answer: It does not support it; the last defined method replaces earlier ones.. Python does not have overloading based on signature; the final definition of a method name in a class scope is the one that stays. Option 0 is false. Option 1 is false. Option 3 is false as abstract classes do not change this core behavior.

    From lesson: Method Overriding and Polymorphism

  181. What is the primary technical purpose of name mangling in Python?

    • To prevent hackers from reading sensitive class data
    • To allow subclasses to define methods without overriding parent methods accidentally
    • To force attributes to be read-only
    • To improve performance by shortening identifier names

    Answer: To allow subclasses to define methods without overriding parent methods accidentally. Mangling allows subclasses to define attributes with the same name as parent attributes without conflict. Option 0 is wrong because mangling is not a security feature. Option 2 is wrong because mangling does not prevent modification. Option 3 is wrong because mangling has no impact on execution speed.

    From lesson: Encapsulation and name mangling

  182. Given 'class A: def __init__(self): self.__val = 10', what happens when you attempt to access 'a = A(); a.__val'?

    • It returns 10
    • It returns None
    • It raises an AttributeError
    • It returns a memory address

    Answer: It raises an AttributeError. Because of name mangling, __val is transformed into _A__val. Accessing __val directly raises an AttributeError because the attribute does not exist under that name. Options 0, 1, and 3 are incorrect as the interpreter cannot find the identifier.

    From lesson: Encapsulation and name mangling

  183. How can you access a double-underscore prefixed variable from outside the class successfully?

    • By using the setattr function with the original name
    • By accessing the attribute via the mangled name _ClassName__variable
    • It is strictly impossible regardless of how you access it
    • By defining a class method that returns the variable

    Answer: By accessing the attribute via the mangled name _ClassName__variable. Python reveals the mangled name, allowing manual access via _ClassName__variable. Option 0 fails because the name is mangled at the class level. Option 2 is false as Python does not enforce private scope. Option 3 is a valid architectural pattern but not the way to access the specific mangled attribute directly.

    From lesson: Encapsulation and name mangling

  184. When does name mangling occur during the execution of a Python program?

    • At runtime, whenever an object is instantiated
    • Whenever the variable is accessed using dot notation
    • During the parsing phase when the class is defined
    • Only when the attribute is assigned a value for the first time

    Answer: During the parsing phase when the class is defined. The interpreter mangles names at parse time within the class body. It does not wait for instantiation (Option 0), does not depend on access type (Option 1), and does not depend on dynamic assignment (Option 3).

    From lesson: Encapsulation and name mangling

  185. Which of the following is the recommended Pythonic way to indicate that an attribute is intended for internal use?

    • Prefixing the name with a double underscore
    • Prefixing the name with a single underscore
    • Using all capital letters for the variable name
    • Wrapping the variable in a private method

    Answer: Prefixing the name with a single underscore. A single underscore is the PEP 8 convention for 'internal' or 'protected' attributes. Option 0 is reserved for avoiding name collisions in subclasses. Option 2 is for constants. Option 3 is unnecessarily complex for simple encapsulation.

    From lesson: Encapsulation and name mangling

  186. If a custom class defines both __str__ and __repr__, which one is called when you type the variable name into the interactive Python shell?

    • __str__
    • __repr__
    • __call__
    • __format__

    Answer: __repr__. The interactive shell uses the repr() function to display objects, which calls __repr__. __str__ is only used by print() or str(). The other methods serve entirely different purposes.

    From lesson: Magic / Dunder Methods

  187. What is the primary purpose of implementing __call__ in a Python class?

    • To allow the class to be instantiated with arguments
    • To allow instances of the class to be treated like functions
    • To define how the object behaves when used in a loop
    • To manage memory allocation for the object

    Answer: To allow instances of the class to be treated like functions. __call__ allows an instance to be invoked with parentheses, effectively behaving like a callable object. __init__ handles instantiation, while the others relate to iteration or memory management.

    From lesson: Magic / Dunder Methods

  188. Why should you typically implement __eq__ when defining a custom class that represents a data entity?

    • To make the object sortable
    • To define how to compare two instances for equality
    • To improve the performance of attribute lookup
    • To allow the object to be added to a list

    Answer: To define how to compare two instances for equality. By default, objects are compared by memory identity. __eq__ allows you to define logical equality based on state. Sorting requires __lt__, and lists don't require equality.

    From lesson: Magic / Dunder Methods

  189. If you implement __getitem__ in your class, what functionality does it provide?

    • Enables subscripting access like instance[key]
    • Enables deleting an item using the del keyword
    • Enables iteration using a for-in loop
    • Enables attribute access using dot notation

    Answer: Enables subscripting access like instance[key]. __getitem__ allows instances to use index or key notation. Iteration requires __iter__, deletion requires __delitem__, and dot notation access is handled by __getattr__ or __getattribute__.

    From lesson: Magic / Dunder Methods

  190. When implementing __add__, why should you return a new instance instead of modifying the existing one?

    • To comply with the requirements of the Python compiler
    • To support the immutable nature of operators like + in expressions
    • To avoid the use of the self keyword
    • To ensure the garbage collector can free the memory

    Answer: To support the immutable nature of operators like + in expressions. The + operator is expected to return a new object (like standard math operations), whereas += (via __iadd__) is used for in-place modifications. Returning the same instance violates the expectation that the original object remains unchanged.

    From lesson: Magic / Dunder Methods

  191. What is the primary technical mechanism that prevents an Abstract Base Class from being instantiated in Python?

    • The class is marked with a private access modifier.
    • The presence of one or more methods decorated with @abstractmethod.
    • The class explicitly raises a TypeError in its __new__ method.
    • The class does not contain an __init__ method.

    Answer: The presence of one or more methods decorated with @abstractmethod.. Option 2 is correct because the 'abc' module's metaclass prevents instantiation if any abstract methods remain unimplemented. Option 1 does not exist in Python. Option 3 is unnecessary as the metaclass handles this. Option 4 is invalid because all classes have a base object initializer.

    From lesson: Abstract Classes and Interfaces

  192. When using 'typing.Protocol', what determines if a class fulfills the interface?

    • The class must explicitly inherit from the Protocol class.
    • The class must be registered with the Protocol using a decorator.
    • The presence of methods and attributes matching the Protocol's structure.
    • The class must be defined in the same module as the Protocol.

    Answer: The presence of methods and attributes matching the Protocol's structure.. Option 3 is correct because Protocols implement structural subtyping (duck typing). Option 1 is false because explicit inheritance is optional. Option 2 is not a requirement. Option 4 is false because Protocols are designed to be decoupled from the implementation.

    From lesson: Abstract Classes and Interfaces

  193. Which of the following best describes the intended use of an Abstract Base Class versus a Protocol?

    • ABCs define a common implementation, while Protocols define a common structure.
    • ABCs are for run-time checks, while Protocols are purely for static type checkers.
    • ABCs require inheritance, while Protocols cannot be used with inheritance.
    • ABCs are faster at runtime, while Protocols significantly slow down performance.

    Answer: ABCs define a common implementation, while Protocols define a common structure.. Option 0 is correct: ABCs are often used for shared code/state (nominal), whereas Protocols focus on what an object can do (structural). Option 1 is misleading as both have runtime implications. Option 2 is incorrect as Protocols support inheritance. Option 3 is false as performance differences are negligible.

    From lesson: Abstract Classes and Interfaces

  194. What happens if a subclass fails to override an abstract method defined in an ABC?

    • Python raises a SyntaxError at the time the subclass is defined.
    • The method defaults to an empty implementation that does nothing.
    • The subclass itself becomes abstract and cannot be instantiated.
    • The program crashes immediately upon importing the module.

    Answer: The subclass itself becomes abstract and cannot be instantiated.. Option 2 is correct: if a subclass doesn't implement all abstract methods, it inherits the abstract status and thus cannot be instantiated. Option 1 is incorrect because definition is valid. Option 0 is false as no default is provided. Option 3 is incorrect as the program only crashes upon attempting instantiation.

    From lesson: Abstract Classes and Interfaces

  195. Why would you choose to use an interface-like structure (like a Protocol) instead of just using normal duck typing without explicit types?

    • To allow Python to run code faster by pre-compiling the interfaces.
    • To enable static type checking tools like Mypy to catch errors early.
    • To restrict the methods an object can have at runtime.
    • To force the object to have a specific __class__ attribute.

    Answer: To enable static type checking tools like Mypy to catch errors early.. Option 1 is correct: interfaces provide a contract for static analysis to verify that objects provide required methods. Option 0 is false. Option 2 is false as Python does not restrict runtime attributes this way. Option 3 is false as the __class__ attribute is inherent to all objects.

    From lesson: Abstract Classes and Interfaces

  196. Which decorator argument prevents a dataclass instance from having its attributes modified after initialization?

    • slots=True
    • frozen=True
    • order=True
    • init=False

    Answer: frozen=True. frozen=True makes the object hashable and immutable. slots=True optimizes memory, order=True enables comparison operators, and init=False disables automatic generation of the constructor.

    From lesson: Dataclasses

  197. How should you correctly define a field that initializes as an empty list for every new instance?

    • data: list = []
    • data: list = field(default=[])
    • data: list = field(default_factory=list)
    • data: list = list()

    Answer: data: list = field(default_factory=list). default_factory takes a callable to create a fresh object per instance. Using [] or default=[] shares the list across all instances, and list() at the class level is invalid syntax for field defaults.

    From lesson: Dataclasses

  198. When should you use the __post_init__ method in a dataclass?

    • To define the types of the fields
    • To perform validation or calculations after the generated __init__ runs
    • To change the order of arguments in the constructor
    • To prevent the class from being inherited

    Answer: To perform validation or calculations after the generated __init__ runs. The __post_init__ method is a hook called automatically after __init__. The other options are handled by the decorator arguments or standard class metadata.

    From lesson: Dataclasses

  199. If you define a dataclass with two fields, one with a default value and one without, what is the required order?

    • Any order is acceptable
    • Default values must come before fields without defaults
    • Fields without defaults must come before fields with defaults
    • They must be grouped in alphabetical order

    Answer: Fields without defaults must come before fields with defaults. Python requires non-default arguments to precede arguments with default values, mirroring standard function signature rules. The other options violate this structural requirement.

    From lesson: Dataclasses

  200. What is the primary effect of using 'slots=True' in a dataclass decorator?

    • It prevents adding new attributes to the instance at runtime
    • It automatically generates a __hash__ method
    • It allows the instance to be used as a dictionary key
    • It makes the class faster by reducing memory consumption

    Answer: It makes the class faster by reducing memory consumption. Slots reduce memory usage by preventing the creation of per-instance __dict__ objects. While it does prevent adding new attributes, that is a side effect, not the primary purpose of the optimization.

    From lesson: Dataclasses

  201. What is the primary advantage of using the 'with' statement when opening a file?

    • It increases the speed at which data is read from the disk.
    • It prevents the program from requiring administrative privileges.
    • It guarantees the file is properly closed even if an exception occurs.
    • It allows multiple processes to write to the same file simultaneously.

    Answer: It guarantees the file is properly closed even if an exception occurs.. The 'with' statement implements a context manager that ensures 'file.close()' is called automatically upon exiting the block, even if an error occurs. Option 0 and 1 are incorrect because performance and privileges are not managed by context managers. Option 3 is incorrect because the 'with' statement does not resolve file locking conflicts.

    From lesson: Reading and Writing Files

  202. If you want to append content to the end of an existing file without deleting its current contents, which mode should you use?

    • r
    • w
    • a
    • x

    Answer: a. 'a' stands for append, which places the file pointer at the end of the file. 'r' is read-only. 'w' truncates the file (deletes contents) before writing. 'x' is for exclusive creation and will raise an error if the file already exists.

    From lesson: Reading and Writing Files

  203. Which of the following is the most memory-efficient way to process a 5GB text file?

    • Use readlines() to get all content as a list.
    • Use a loop to iterate over the file object directly.
    • Use read() and store the string in a variable.
    • Use read(1024) to read the whole file in chunks of one kilobyte.

    Answer: Use a loop to iterate over the file object directly.. Iterating over the file object ('for line in file') reads only one line into memory at a time, keeping usage minimal. 'readlines()' and 'read()' load everything into memory, which would crash on a 5GB file. Option 3 is unnecessarily complex compared to simple iteration.

    From lesson: Reading and Writing Files

  204. What happens if you open a file in 'w' mode that does not exist?

    • Python raises a FileNotFoundError.
    • Python creates a new, empty file.
    • The operation is ignored and the program continues.
    • The program prompts the user to create the file.

    Answer: Python creates a new, empty file.. In both 'w' (write) and 'a' (append) modes, Python automatically creates the file if it does not already exist. 'r' is the only common mode that raises a FileNotFoundError if the file is missing.

    From lesson: Reading and Writing Files

  205. When writing to a file, why might a program's output not appear immediately in the file even after the 'write()' method executes?

    • The operating system is waiting for user confirmation.
    • Python automatically deletes the content if the file is small.
    • The data is buffered in memory to optimize disk I/O operations.
    • The write() method is asynchronous and cannot be finished immediately.

    Answer: The data is buffered in memory to optimize disk I/O operations.. Python (and the OS) buffers writes to minimize expensive disk operations. Data is only flushed to the actual storage when the buffer is full, the file is closed, or flush() is called manually. Options 0, 1, and 3 are factually incorrect regarding Python's I/O behavior.

    From lesson: Reading and Writing Files

  206. What is the primary purpose of the __exit__ method in a context manager?

    • To initialize the resources required by the context manager.
    • To define the setup code that runs before the block starts.
    • To handle cleanup tasks like closing files or releasing locks, even if an exception occurs.
    • To return the object that will be bound to the variable after the 'as' keyword.

    Answer: To handle cleanup tasks like closing files or releasing locks, even if an exception occurs.. The __exit__ method is specifically designed for teardown/cleanup logic. Option 0 and 1 are handled by __init__ and __enter__ respectively. Option 3 is the purpose of __enter__.

    From lesson: Context Managers — the with statement

  207. What happens to a file opened with 'with open(...) as f:' if an exception is raised inside the block?

    • The file remains open until the script finishes execution.
    • The file is closed automatically before the exception propagates.
    • The exception is swallowed and the file is closed.
    • The file is left in an indeterminate state.

    Answer: The file is closed automatically before the exception propagates.. The 'with' statement guarantees that __exit__ is called, which ensures the file is closed regardless of whether the block finishes successfully or raises an exception. The others are incorrect because they imply resource leakage or unsafe handling.

    From lesson: Context Managers — the with statement

  208. Which of the following describes the correct behavior when using multiple context managers in a single 'with' statement?

    • Only the first context manager is guaranteed to close if the second one fails to initialize.
    • The context managers are entered left-to-right and exited in the reverse order.
    • The context managers are entered and exited simultaneously to prevent deadlocks.
    • You must nest the 'with' statements for them to work correctly.

    Answer: The context managers are entered left-to-right and exited in the reverse order.. Multiple context managers (e.g., 'with A() as a, B() as b:') follow a LIFO (Last-In-First-Out) order for exiting. Nesting is an alternative, but the comma-separated syntax is a native feature that follows this specific order.

    From lesson: Context Managers — the with statement

  209. If you are creating a class to use with the 'with' statement, what must it contain?

    • Only an __init__ method.
    • A __call__ method to execute the block.
    • Both __enter__ and __exit__ methods.
    • A __context__ property.

    Answer: Both __enter__ and __exit__ methods.. The context manager protocol strictly requires __enter__ (to prepare) and __exit__ (to clean up). The other methods listed are unrelated to the context manager protocol.

    From lesson: Context Managers — the with statement

  210. What value does the 'as' clause in a 'with' statement receive?

    • The return value of the __enter__ method.
    • The return value of the __exit__ method.
    • The instance of the class being used as the context manager.
    • A boolean indicating if the block was successful.

    Answer: The return value of the __enter__ method.. The object assigned to the variable after 'as' is whatever the __enter__ method returns. It is often 'self', but can be any object. The other options do not reflect the standard protocol behavior.

    From lesson: Context Managers — the with statement

  211. What is the primary purpose of the 'else' block in a try-except structure?

    • To execute code only if an exception occurred in the try block.
    • To execute code only if no exceptions were raised in the try block.
    • To perform cleanup operations regardless of whether an exception occurred.
    • To handle specific exceptions that were not caught by the except block.

    Answer: To execute code only if no exceptions were raised in the try block.. The 'else' block runs only if the code in the 'try' block succeeds without raising an exception. Option 0 describes the 'except' block, option 2 describes the 'finally' block, and option 3 is logically incorrect as 'else' does not handle errors.

    From lesson: try / except / else / finally

  212. If an exception is raised in the 'try' block, in what order does the control flow proceed if an 'except', 'else', and 'finally' are all present?

    • try -> else -> finally
    • try -> except -> else -> finally
    • try -> except -> finally
    • try -> finally -> except

    Answer: try -> except -> finally. If an error occurs, the 'else' block is skipped. The 'except' block executes, followed by 'finally'. Option 0 and 1 are wrong because 'else' only executes on success. Option 3 is wrong because 'except' must precede 'finally'.

    From lesson: try / except / else / finally

  213. Consider a function that opens a file. If the file processing fails, you want to close the file and then propagate the error. Where should the file.close() call go?

    • In an 'else' block.
    • At the end of the 'try' block.
    • In a 'finally' block.
    • In the 'except' block.

    Answer: In a 'finally' block.. The 'finally' block is guaranteed to run regardless of whether an exception occurred or was caught. If you put it in the 'try' or 'else' blocks, it might be skipped if an error occurs. The 'except' block would only run if an error happens, not if the file processes successfully.

    From lesson: try / except / else / finally

  214. What happens if a 'return' statement exists in both the 'try' block and the 'finally' block?

    • The 'try' block's return value is returned.
    • The function returns None.
    • The 'finally' block's return value overrides the 'try' block's value.
    • The code raises a SyntaxError.

    Answer: The 'finally' block's return value overrides the 'try' block's value.. When a 'finally' block contains a return statement, it overrides any return statement that was triggered in the 'try' or 'except' blocks. This is a common source of confusion, making option 0 incorrect.

    From lesson: try / except / else / finally

  215. Which of the following is the most Pythonic way to handle a division by zero error while ensuring a message is printed?

    • Use an if-statement to check if the divisor is zero.
    • Wrap the division in a try-except ZeroDivisionError block.
    • Use a bare except: block to catch the error.
    • Put the code in a finally block to avoid the crash.

    Answer: Wrap the division in a try-except ZeroDivisionError block.. In Python, it is often preferred to 'Ask for Forgiveness' (EAFP) using specific exception handling. Option 0 (LBYL) is valid but doesn't use the requested keywords. Option 2 is bad practice (catches too much), and option 3 doesn't prevent the crash, as 'finally' does not suppress exceptions.

    From lesson: try / except / else / finally

  216. When should you use the 'IndexError' exception rather than 'KeyError'?

    • When accessing a value in a dictionary with a missing key
    • When accessing a sequence element with an out-of-range integer
    • When a function receives an argument of the wrong type
    • When a file operation fails to find a specified path

    Answer: When accessing a sequence element with an out-of-range integer. IndexError is for sequences like lists or strings. KeyError is for dictionaries. TypeError is for wrong types, and FileNotFoundError is for missing files.

    From lesson: Common Built-in Exceptions

  217. Which of the following is the most Pythonic way to handle a potential division by zero error?

    • Check if the denominator is zero using an 'if' statement
    • Wrap the division in a 'try-except ZeroDivisionError' block
    • Use 'try-except Exception' for all operations
    • Import the math module to check the value before division

    Answer: Wrap the division in a 'try-except ZeroDivisionError' block. Using try-except follows the EAFP principle. 'If' statements are less robust against concurrent changes. Catching all 'Exception' is too broad, and importing math doesn't prevent runtime errors.

    From lesson: Common Built-in Exceptions

  218. What happens if you raise an exception inside a 'try' block that is not caught by any 'except' clause?

    • The 'finally' block is skipped and the program crashes
    • The error is silently ignored and the program continues
    • The exception propagates up the call stack until caught or the program terminates
    • The 'else' block is executed instead

    Answer: The exception propagates up the call stack until caught or the program terminates. Python bubbles the exception up the stack. 'Finally' always runs even if not caught. The program does not ignore errors, and 'else' only runs if no exception occurred.

    From lesson: Common Built-in Exceptions

  219. What is the primary difference between 'ValueError' and 'TypeError'?

    • ValueError is for logic errors, TypeError is for syntax errors
    • ValueError is for inappropriate argument values, TypeError is for inappropriate object types
    • TypeError is for math errors, ValueError is for object errors
    • There is no difference, they are interchangeable

    Answer: ValueError is for inappropriate argument values, TypeError is for inappropriate object types. ValueError is for when the object type is correct but the value is unacceptable (e.g., int('a')). TypeError is for when an operation is performed on an incompatible type.

    From lesson: Common Built-in Exceptions

  220. In a 'try-except-else-finally' construct, when does the 'else' block execute?

    • Whenever the 'try' block finishes execution
    • Only when an exception is raised in the 'try' block
    • Only when no exception is raised in the 'try' block
    • Before the 'except' block, if an error occurs

    Answer: Only when no exception is raised in the 'try' block. The 'else' block runs only if the code in the 'try' block completes without raising an exception. It is not for exceptions; 'finally' is for cleanup.

    From lesson: Common Built-in Exceptions

  221. Which of the following is the recommended best practice when defining a custom exception in Python?

    • Inherit from the object class.
    • Inherit from the Exception class.
    • Implement a custom __str__ method without calling super().
    • Use a function instead of a class.

    Answer: Inherit from the Exception class.. Inheriting from Exception ensures compatibility with standard error handling blocks. Option 1 makes it unusable as an error, option 3 ignores base class initialization, and option 4 is not how Python handles exceptions.

    From lesson: Raising Custom Exceptions

  222. What is the primary reason to raise a custom exception instead of a built-in one like ValueError?

    • To make the code run faster.
    • To hide the internal workings of the library.
    • To provide specific, domain-contextual error information to the caller.
    • Because custom exceptions are required by the Python interpreter for all classes.

    Answer: To provide specific, domain-contextual error information to the caller.. Custom exceptions allow callers to distinguish between specific domain-level failures and generic system errors. Options 1 and 4 are false, and option 2 is generally an anti-pattern.

    From lesson: Raising Custom Exceptions

  223. When defining a custom exception, why is it important to include 'super().__init__(*args)' inside the custom class's __init__ method?

    • It is required by the Python compiler for all classes.
    • It prevents the exception from crashing the program.
    • It ensures that the exception arguments are correctly stored in the base Exception structure.
    • It automatically logs the exception to a file.

    Answer: It ensures that the exception arguments are correctly stored in the base Exception structure.. The base Exception class manages internal storage for error arguments; failing to call super prevents these from being accessible. The others are incorrect functional claims.

    From lesson: Raising Custom Exceptions

  224. How should an application handle a custom exception that it expects to occur during normal operation?

    • By catching it with an 'except' block specifically targeting that class.
    • By letting the program terminate whenever it is raised.
    • By wrapping the entire application in a 'try...except' block.
    • By returning a null value instead of raising the exception.

    Answer: By catching it with an 'except' block specifically targeting that class.. Targeted exception handling is precise and prevents silent errors. Options 1 and 3 are too broad or rely on non-pythonic logic, and 4 defeats the purpose of the exception system.

    From lesson: Raising Custom Exceptions

  225. Which scenario most justifies the creation of a custom exception class?

    • You need a variable to hold a temporary value in a function.
    • You need to signal a specific business-logic failure that built-in exceptions do not clearly represent.
    • You want to save memory by avoiding built-in exception types.
    • You are writing a script that does not require any error handling.

    Answer: You need to signal a specific business-logic failure that built-in exceptions do not clearly represent.. Custom exceptions are for signaling specific, domain-relevant errors. Option 1 describes a variable, option 3 is false as they have similar memory usage, and 4 makes the exception irrelevant.

    From lesson: Raising Custom Exceptions

  226. What is the primary effect of the '@' syntax in Python?

    • It marks a function as asynchronous for the event loop.
    • It passes a function as an argument to another function and reassigns the result.
    • It prevents the function from being called multiple times.
    • It automatically compiles the function into machine code.

    Answer: It passes a function as an argument to another function and reassigns the result.. The @ decorator syntax is syntactic sugar for func = decorator(func). Option 0 is wrong because async uses the 'async' keyword. Option 2 describes memoization, not the decorator mechanism itself. Option 3 is incorrect as Python does not compile to machine code via decorators.

    From lesson: Decorators

  227. If you define a decorator with arguments, how many layers of nested functions do you typically need?

    • One layer: the wrapper function.
    • Two layers: the decorator and the wrapper.
    • Three layers: the decorator factory, the decorator, and the wrapper.
    • Four layers: to handle global state.

    Answer: Three layers: the decorator factory, the decorator, and the wrapper.. A decorator that takes arguments requires a factory function that returns the decorator, which in turn returns the wrapper. Option 0 and 1 are insufficient for parameterized decorators, and 3 is unnecessary complexity.

    From lesson: Decorators

  228. Why is it important to use functools.wraps within a decorator?

    • It allows the decorated function to run faster.
    • It forces the decorator to only run on specific data types.
    • It preserves the original function's docstring and name.
    • It enables the function to be called with keyword arguments.

    Answer: It preserves the original function's docstring and name.. functools.wraps copies the metadata (name, docstring, etc.) from the wrapped function to the wrapper. Option 0 is false as it adds overhead. Option 1 is false as it's not a type-checking tool. Option 3 is false as it does not affect argument passing.

    From lesson: Decorators

  229. Consider a wrapper function defined as 'def wrapper(*args, **kwargs):'. Why are *args and **kwargs used?

    • To ensure the decorator can accept any number of positional and keyword arguments.
    • To define the function as a generator.
    • To allow the function to be executed in parallel.
    • To tell the Python interpreter to ignore type checking.

    Answer: To ensure the decorator can accept any number of positional and keyword arguments.. Using *args and **kwargs ensures the decorator is generic and can wrap functions regardless of their signature. Option 1 refers to 'yield'. Option 2 refers to threading or multiprocessing. Option 3 is unrelated to signature handling.

    From lesson: Decorators

  230. What happens if a decorator function does not return the inner wrapper function?

    • The original function is executed automatically.
    • The original function is replaced by None or whatever the decorator returns.
    • The program crashes immediately upon script execution.
    • The decorator is applied but only runs once.

    Answer: The original function is replaced by None or whatever the decorator returns.. Decorators replace the original function with the return value of the decorator function. If nothing is returned, the variable becomes None. Option 0 is wrong because it returns the object, not the result. Option 2 is wrong as it won't crash until the function is called. Option 3 is a misunderstanding of how decorators bind.

    From lesson: Decorators

  231. What happens when a generator function is called in Python?

    • The function body executes until the first yield statement.
    • The function body executes until the first return statement.
    • A generator object is returned, and the function body is not yet executed.
    • The function executes completely and returns a list of values.

    Answer: A generator object is returned, and the function body is not yet executed.. Calling a generator function returns an iterator object without executing the function body. Option 1 is wrong because execution is lazy. Option 2 is wrong because return ends the generator. Option 4 is wrong because generators do not store all values.

    From lesson: Generators and the yield keyword

  232. Which of the following describes the memory behavior of a generator?

    • It loads all generated elements into RAM at once for fast access.
    • It stores only the current state, generating values on demand.
    • It uses a fixed amount of memory regardless of the number of elements yielded.
    • It requires a memory buffer to hold the yielded results.

    Answer: It stores only the current state, generating values on demand.. Generators are lazy, storing only the state required to produce the next item. Option 1 describes a list. Option 3 is wrong because local variables within the generator function can affect memory usage. Option 4 is incorrect as no output buffer is required.

    From lesson: Generators and the yield keyword

  233. What is the primary difference between 'yield' and 'yield from'?

    • yield is for lists, yield from is for dictionaries.
    • yield pauses the function, yield from terminates the generator immediately.
    • yield emits a single value, while yield from delegates iteration to a sub-iterable.
    • yield from is faster because it bypasses the function call stack.

    Answer: yield emits a single value, while yield from delegates iteration to a sub-iterable.. yield from is a syntax sugar used to delegate to another iterator, pulling all items from it. Option 1 is wrong as both work with iterables. Option 2 is wrong as yield from doesn't terminate the parent. Option 4 is incorrect regarding performance.

    From lesson: Generators and the yield keyword

  234. Given 'gen = (x**2 for x in range(3))', what is the result of list(gen) + list(gen)?

    • [0, 1, 4, 0, 1, 4]
    • [0, 1, 4]
    • []
    • A TypeError is raised.

    Answer: [0, 1, 4]. Generators are exhausted after the first iteration. The first list() call consumes all values; the second call receives an empty iterator. Options 1 is wrong because the generator doesn't reset. Option 3 is wrong because the first list is created successfully.

    From lesson: Generators and the yield keyword

  235. How can you pass a value back into a running generator?

    • By using the send() method on the generator object.
    • By assigning a value to the yield keyword.
    • By calling next() with an argument.
    • It is impossible to pass data into a generator once it starts.

    Answer: By using the send() method on the generator object.. The send() method allows passing a value into a generator, which then becomes the result of the yield expression. Option 1 is wrong as yield is a statement, not a variable. Option 3 is wrong as next() only takes the generator object.

    From lesson: Generators and the yield keyword

  236. What happens when the __next__() method of an iterator is called after the iterator has been exhausted?

    • It returns None
    • It restarts from the first element
    • It raises a StopIteration exception
    • It returns the last yielded value

    Answer: It raises a StopIteration exception. The iterator protocol mandates that StopIteration is raised to signal the end of data. Returning None or restarting would be ambiguous, and returning the last value would violate the protocol.

    From lesson: Iterators and the Iterator Protocol

  237. Which of the following describes the relationship between an iterable and an iterator?

    • An iterable must implement __next__, while an iterator must implement __iter__
    • An iterator is an object that implements both __iter__ and __next__
    • An iterable must be a list or tuple
    • An iterator cannot be an iterable

    Answer: An iterator is an object that implements both __iter__ and __next__. An iterator must return itself from __iter__ to be used in loops, and implement __next__ to return values. The other options are incorrect because iterators can be iterables, and iterables only strictly require __iter__.

    From lesson: Iterators and the Iterator Protocol

  238. Consider code that calls iter() on a list. What is the type of the returned object?

    • A list_iterator
    • A new list
    • A generator object
    • The original list object

    Answer: A list_iterator. Calling iter() on a sequence creates a built-in iterator specifically designed for that sequence type. It is not a new list, a generator, or the original object itself.

    From lesson: Iterators and the Iterator Protocol

  239. When you use a for-loop to iterate over an object, what is the very first step the Python interpreter performs?

    • Calls __next__() on the object
    • Calls __getitem__() on the object
    • Checks if the object is a list
    • Calls __iter__() on the object

    Answer: Calls __iter__() on the object. The loop protocol requires obtaining an iterator first by calling __iter__(). The other options bypass the iterator protocol, which is fundamental to how Python's loops operate.

    From lesson: Iterators and the Iterator Protocol

  240. If you define a class with a __next__() method but no __iter__() method, can it be used directly in a for-loop?

    • Yes, because Python implicitly uses __next__
    • No, because the for-loop expects an __iter__ method
    • Yes, but only if it inherits from object
    • No, but it will work with the next() built-in function

    Answer: No, because the for-loop expects an __iter__ method. A for-loop always calls iter() on the provided object to get an iterator. Without an __iter__ method, the object is not considered iterable, even if it has a __next__ method.

    From lesson: Iterators and the Iterator Protocol

  241. When does the __enter__ method of a class-based context manager execute?

    • Immediately after the class instance is created in the __init__ method.
    • When the execution flow enters the block defined by the 'with' statement.
    • Only if an exception is raised within the code block.
    • After the 'with' block has finished executing.

    Answer: When the execution flow enters the block defined by the 'with' statement.. __enter__ runs exactly when the 'with' block starts. Option 0 is wrong because __init__ is for setup, not context entering. Option 2 is wrong because __enter__ always runs regardless of exceptions. Option 3 describes the purpose of __exit__.

    From lesson: Context Managers (class-based)

  242. What is the result of returning False from the __exit__ method when an exception occurs inside the 'with' block?

    • The exception is caught and the program continues after the 'with' block.
    • The exception is silenced and no error is reported.
    • The exception propagates out of the 'with' block normally.
    • The exception is converted into a warning.

    Answer: The exception propagates out of the 'with' block normally.. Returning a falsy value (or None) from __exit__ tells Python to re-raise the exception. Option 0 and 1 are incorrect because they describe suppressing the exception (True). Option 3 is incorrect as warnings are unrelated to flow control.

    From lesson: Context Managers (class-based)

  243. If you want to use an object in a 'with' statement like 'with MyClass() as obj:', what must __enter__ return?

    • The object itself (self).
    • The class instance's __init__ method.
    • The value True.
    • A list of all objects managed by the class.

    Answer: The object itself (self).. The value assigned to the variable after 'as' is the return value of __enter__. Returning 'self' is standard practice. Option 1 is invalid syntax, option 2 is incorrect logic, and option 3 would make 'obj' equal to True.

    From lesson: Context Managers (class-based)

  244. Why is it important to implement __exit__ even if you don't intend to handle exceptions?

    • To define how the object is serialized.
    • To ensure cleanup tasks like closing files or sockets happen automatically.
    • To prevent the garbage collector from deleting the object.
    • To allow multiple 'with' statements to share the same instance.

    Answer: To ensure cleanup tasks like closing files or sockets happen automatically.. The main purpose of a context manager is resource management (RAII). Option 0 is for pickling, option 2 is unrelated to manual memory management, and option 3 is not the responsibility of the context manager.

    From lesson: Context Managers (class-based)

  245. What happens if __enter__ fails (raises an exception) before the 'with' block is entered?

    • The __exit__ method is called with exception details.
    • The __exit__ method is called with None arguments.
    • The __exit__ method is never called.
    • The code block inside the 'with' statement still executes.

    Answer: The __exit__ method is never called.. If __enter__ fails, the code block never runs and the context was never established, so __exit__ is not called. Option 0 and 1 are incorrect because the manager is not fully initialized. Option 3 is impossible if the block never executes.

    From lesson: Context Managers (class-based)

  246. What is the primary difference between partial and a lambda function when creating a new callable?

    • partial is faster because it is implemented in C and can be optimized by the interpreter
    • partial captures the arguments at the time of definition, while lambda evaluates them at runtime
    • partial allows for introspection of the function and arguments, which lambdas do not support well
    • partial can only be used with built-in functions, whereas lambda works with any object

    Answer: partial allows for introspection of the function and arguments, which lambdas do not support well. Option 3 is correct because partial objects have .func, .args, and .keywords attributes, making them introspectable. Option 1 is misleading as speed difference is negligible. Option 2 is incorrect as both capture environments. Option 4 is false as partial works with any callable.

    From lesson: functools — partial, reduce, lru_cache

  247. You use reduce(lambda x, y: x + y, [1, 2, 3], 10). What is the result?

    • 6
    • 16
    • 15
    • An error

    Answer: 16. Option 2 is correct because the initial value 10 is the starting accumulator; 10+1=11, 11+2=13, 13+3=16. Options 1, 3, and 4 are incorrect because they fail to account for the initial value correctly or assume the operation is invalid.

    From lesson: functools — partial, reduce, lru_cache

  248. If you apply lru_cache(maxsize=128) to a recursive function, what happens if the cache fills up?

    • The program raises a MemoryError
    • The cache stops storing new results
    • The least recently used entries are discarded to make room for new ones
    • The cache is wiped entirely and restarts

    Answer: The least recently used entries are discarded to make room for new ones. Option 3 is the definition of Least Recently Used behavior. Option 1 is incorrect because it is designed to manage memory. Option 2 is incorrect as it would lose the benefit of the cache. Option 4 is incorrect as it would be inefficient.

    From lesson: functools — partial, reduce, lru_cache

  249. When using partial on a function with keyword arguments, how can you override the pre-filled arguments?

    • By passing the same keyword argument again in the final call
    • By calling the partial object with new values for those specific keywords
    • It is impossible to override partial arguments; they are immutable
    • By using the .keywords attribute to reassign values

    Answer: By calling the partial object with new values for those specific keywords. Option 2 is correct because providing the same keyword argument in the final call overrides the pre-filled one. Option 1 is technically describing the same action, but option 2 is the standard phrasing. Option 3 is false, and option 4 is not the intended way to modify calls.

    From lesson: functools — partial, reduce, lru_cache

  250. Which of these is the most appropriate use case for reduce?

    • Filtering a list to keep only even numbers
    • Converting a list of numbers into a single aggregate result
    • Mapping each element to a new value in a transformed list
    • Sorting a collection based on a custom key

    Answer: Converting a list of numbers into a single aggregate result. Option 2 is the purpose of reduce (folding). Option 1 and 3 are better served by filter and map (or list comprehensions). Option 4 is better served by the sorted function or list.sort().

    From lesson: functools — partial, reduce, lru_cache

  251. If you have a Python script that calculates prime numbers across four threads on a quad-core processor, why might it not run faster than a single-threaded version?

    • The OS scheduler favors the main thread by default.
    • The Global Interpreter Lock allows only one thread to execute Python bytecode at a time.
    • Python threads are not true OS-level threads.
    • Prime calculation requires constant memory allocation that is blocked by the OS.

    Answer: The Global Interpreter Lock allows only one thread to execute Python bytecode at a time.. Option 1 is incorrect because the OS treats all threads fairly. Option 2 is correct because the GIL restricts execution to one thread at a time, preventing CPU-bound tasks from gaining speedup. Option 3 is false as Python threads are wrappers around native OS threads. Option 4 is false; the memory allocation isn't the primary bottleneck for this behavior.

    From lesson: Multithreading with threading

  252. What is the primary benefit of using 'threading' in a Python application that performs network requests?

    • It speeds up the processing of data returned by the server.
    • It bypasses the GIL to allow multi-core utilization.
    • It allows the program to initiate new requests while waiting for others to finish.
    • It automatically optimizes the memory usage of the network buffers.

    Answer: It allows the program to initiate new requests while waiting for others to finish.. Option 1 is wrong because processing returned data is CPU-bound. Option 2 is wrong because the GIL still exists. Option 3 is correct because network I/O releases the GIL, allowing threads to overlap their waiting times. Option 4 is incorrect as threading does not perform buffer optimization.

    From lesson: Multithreading with threading

  253. What happens if you omit the 'join()' method call on a standard thread in a Python script?

    • The thread will throw a RuntimeError immediately.
    • The thread will continue to run in the background after the main script completes.
    • The thread will be automatically terminated by the interpreter as soon as the main thread completes.
    • The Python script will wait for the thread anyway.

    Answer: The thread will be automatically terminated by the interpreter as soon as the main thread completes.. Option 1 is false; threads don't require joining to be created. Option 2 is misleading as the process exits, killing non-daemon threads. Option 3 is correct; non-daemon threads are killed when the main thread stops. Option 4 is false; the program will not wait if the thread is not joined.

    From lesson: Multithreading with threading

  254. When is it appropriate to use a 'threading.Lock'?

    • Whenever you create more than two threads.
    • When multiple threads need to write to a shared dictionary or list simultaneously.
    • To force threads to execute in a specific order.
    • To stop the GIL from interrupting a thread.

    Answer: When multiple threads need to write to a shared dictionary or list simultaneously.. Option 1 is wrong; threads are not required to be locked unless they share mutable data. Option 2 is correct as shared mutable structures are prone to race conditions. Option 3 is wrong because Locks provide mutual exclusion, not strict sequencing. Option 4 is wrong because you cannot interact with or stop the GIL from a standard thread.

    From lesson: Multithreading with threading

  255. Which of these best describes a 'Daemon' thread?

    • A thread that has higher priority than the main thread.
    • A thread that is killed automatically when the main program finishes.
    • A thread that is protected from being interrupted by the OS.
    • A thread that is specifically designed for CPU-intensive calculations.

    Answer: A thread that is killed automatically when the main program finishes.. Option 1 is false; daemon status is about lifecycle, not priority. Option 2 is correct; daemon threads do not block the program from exiting. Option 3 is false; threads can always be interrupted. Option 4 is false; daemon status has nothing to do with computational complexity.

    From lesson: Multithreading with threading

  256. Why is the 'if __name__ == "__main__":' guard required when using multiprocessing on Windows?

    • It is required by the operating system to allocate CPU cycles.
    • It prevents the child process from re-executing the module's initialization code recursively.
    • It is used to explicitly declare which variables are shared across processes.
    • It allows the Python interpreter to bypass the Global Interpreter Lock entirely.

    Answer: It prevents the child process from re-executing the module's initialization code recursively.. The guard is essential because Windows uses the 'spawn' start method, which re-imports the main script in child processes; without the guard, the code would restart and trigger a fork-bomb. The other options are incorrect because the guard does not manage CPU cycles, IPC, or the GIL.

    From lesson: Multiprocessing Basics

  257. What is the primary difference between a Thread and a Process in Python?

    • Threads share the same memory space, while Processes have their own private memory space.
    • Processes have a Global Interpreter Lock, while Threads do not.
    • Threads are faster than Processes regardless of whether the task is I/O-bound or CPU-bound.
    • Processes can only perform I/O-bound tasks, while Threads can perform CPU-bound tasks.

    Answer: Threads share the same memory space, while Processes have their own private memory space.. Processes are isolated with separate memory, whereas threads share memory, making processes safer but more memory-intensive. The GIL applies to the interpreter, affecting threads, and processes are generally preferred for CPU-bound tasks.

    From lesson: Multiprocessing Basics

  258. If you need to aggregate results from several worker processes, which object is most appropriate for thread-safe/process-safe communication?

    • A standard Python dictionary initialized before process creation.
    • A global variable defined at the top of the script.
    • A multiprocessing.Queue object.
    • A local variable passed as an argument to the process constructor.

    Answer: A multiprocessing.Queue object.. A Queue is specifically designed for process communication and handles the necessary locking internally. Dictionaries, global variables, and local variables are not shared across process boundaries and will not provide synchronized updates.

    From lesson: Multiprocessing Basics

  259. When is it appropriate to use the multiprocessing.Pool class instead of manually creating Process objects?

    • When you need to manually manage the lifecycle and state of every individual child process.
    • When your application needs to handle low-level process signals like SIGKILL.
    • When you have a large dataset and need to distribute a function across a fixed number of workers.
    • When you are performing heavy I/O operations that require waiting for individual file locks.

    Answer: When you have a large dataset and need to distribute a function across a fixed number of workers.. Pool is designed for distributing a task across a fixed number of workers (e.g., via map or starmap), which is far more efficient than manual management for bulk data. Manual process management is for specific control, not bulk execution.

    From lesson: Multiprocessing Basics

  260. Which of the following describes the limitation of using a multiprocessing.Value object to share data?

    • It can only store one integer or float at a time.
    • It requires a custom socket connection to sync between processes.
    • It is automatically locked, but you cannot perform complex operations atomically without an additional Lock.
    • It is only accessible by the main process and not by child processes.

    Answer: It is automatically locked, but you cannot perform complex operations atomically without an additional Lock.. Value objects provide a shared memory location and automatic locking for a single read/write, but operations like 'x += 1' are not atomic, requiring an explicit Lock to prevent race conditions. The other options misstate the functionality or access rules of shared memory.

    From lesson: Multiprocessing Basics

  261. What is the result of calling a function defined with 'async def' without the 'await' keyword?

    • The function executes immediately and returns None
    • The function executes immediately and returns its actual result
    • The function returns a coroutine object without executing its body
    • The interpreter raises a SyntaxError

    Answer: The function returns a coroutine object without executing its body. Calling an async function returns a coroutine object. It must be awaited, yielded from, or scheduled on the event loop to execute. The other options are incorrect because the code body does not run until scheduled, and the syntax itself is valid.

    From lesson: asyncio and async/await

  262. Why does using 'time.sleep()' inside an 'async def' function defeat the purpose of using asyncio?

    • It causes the event loop to crash with a TimeoutError
    • It blocks the thread, preventing the event loop from switching to other ready tasks
    • It forces the event loop to spawn a new thread for the function
    • It causes the function to run synchronously in the background

    Answer: It blocks the thread, preventing the event loop from switching to other ready tasks. The event loop is single-threaded. 'time.sleep()' is a blocking operation that stops the entire thread. 'asyncio.sleep()' is the correct non-blocking version. The other options describe behaviors that do not occur.

    From lesson: asyncio and async/await

  263. What is the primary difference between 'asyncio.create_task()' and 'await'ing a coroutine directly?

    • 'create_task' runs the code on a separate OS thread
    • 'await' runs the code on a separate OS thread
    • 'create_task' schedules the coroutine to run on the loop as soon as possible, while 'await' pauses the current function until it finishes
    • 'create_task' is for CPU-bound tasks, 'await' is for I/O-bound tasks

    Answer: 'create_task' schedules the coroutine to run on the loop as soon as possible, while 'await' pauses the current function until it finishes. 'create_task' queues the task for the event loop, allowing execution to continue immediately. 'await' pauses the caller until the specific task completes. Neither creates a new OS thread.

    From lesson: asyncio and async/await

  264. When using 'asyncio.gather(*tasks)', what happens if one of the tasks raises an unhandled exception?

    • The event loop is automatically terminated
    • The exception is ignored, and the function returns None
    • The exception is raised to the caller when 'gather' is awaited
    • All other tasks in the gather call are automatically cancelled

    Answer: The exception is raised to the caller when 'gather' is awaited. 'asyncio.gather' propagates the exception to the caller when the gathered result is awaited. It does not automatically cancel other tasks unless specifically configured, and it certainly does not ignore the error or crash the loop.

    From lesson: asyncio and async/await

  265. What is the correct way to run a top-level async function from a synchronous script?

    • Use 'asyncio.run(main())'
    • Use 'await main()'
    • Use 'asyncio.get_event_loop().run_forever(main())'
    • Use 'loop.create_task(main())'

    Answer: Use 'asyncio.run(main())'. 'asyncio.run()' is the recommended modern entry point that handles loop creation and cleanup. 'await' is illegal in synchronous code. 'run_forever' is lower-level and not intended for executing a single entry-point function.

    From lesson: asyncio and async/await

  266. What is the primary purpose of running mypy on a Python codebase?

    • To optimize the execution speed of Python scripts
    • To perform static analysis and identify potential type inconsistencies
    • To automatically generate documentation for function signatures
    • To convert Python code into machine code for better performance

    Answer: To perform static analysis and identify potential type inconsistencies. Mypy checks for type consistency without running the code. Option 0 and 3 are incorrect because mypy does not affect execution speed or compilation. Option 2 is incorrect as tools like Sphinx handle documentation.

    From lesson: Type Hints and mypy

  267. Given 'def process(items: list[str]) -> None:', which of the following is a valid usage that passes type checking?

    • process([1, 2, 3])
    • process('hello')
    • process(['a', 'b', 'c'])
    • process(None)

    Answer: process(['a', 'b', 'c']). The type hint expects a list of strings. Option 0 provides integers, 1 provides a string, and 3 provides None, all of which violate the 'list[str]' signature.

    From lesson: Type Hints and mypy

  268. If a function argument can be either an 'int' or a 'float', how should it be correctly type-hinted?

    • Union[int, float]
    • Any
    • int | int
    • Optional[int]

    Answer: Union[int, float]. Union represents a set of possible types. Option 1 is too broad, 2 is redundant, and 3 is invalid syntax. 4 indicates an integer that might be None, not a float.

    From lesson: Type Hints and mypy

  269. What does the 'Optional[int]' type hint imply in Python type checking?

    • The integer value is mandatory and cannot be None
    • The function can either return an integer or skip execution
    • The value is either an integer or None
    • The integer size is not defined and can be any length

    Answer: The value is either an integer or None. Optional[T] is an alias for Union[T, None]. Option 0 is the behavior of 'int', 1 is syntactically irrelevant, and 3 is a misunderstanding of how Python handles integer precision.

    From lesson: Type Hints and mypy

  270. When defining a function that returns nothing, why is '-> None' preferred over omitting the return hint?

    • It makes the code run faster
    • It prevents the developer from returning values by mistake
    • It is required by the Python interpreter to execute the code
    • It allows the function to accept any return type

    Answer: It prevents the developer from returning values by mistake. Explicitly hinting '-> None' tells mypy that the function should not return anything. If you omit it, mypy defaults to 'Any', potentially letting unintended return values go unnoticed. Options 0, 2, and 3 are technically incorrect.

    From lesson: Type Hints and mypy

  271. Which approach is the most robust way to create a path to a file inside a subdirectory on multiple operating systems?

    • Path('folder') + '/' + Path('file.txt')
    • Path('folder') / 'file.txt'
    • os.path.join('folder', 'file.txt').replace('\\', '/')
    • Path('folder').joinpath('file.txt').as_posix()

    Answer: Path('folder') / 'file.txt'. Using the / operator with pathlib handles OS-specific separators automatically. The + operator is meant for strings, manual replacements are error-prone, and as_posix() doesn't change how the path is joined.

    From lesson: os and pathlib Modules

  272. Why is 'pathlib.Path(__file__).parent' preferred over 'os.getcwd()' when you need to access a file relative to your script?

    • It is faster than calling os functions.
    • It prevents race conditions during file access.
    • It is relative to the script file location, whereas getcwd() is relative to where the terminal is currently positioned.
    • It automatically resolves symbolic links before execution.

    Answer: It is relative to the script file location, whereas getcwd() is relative to where the terminal is currently positioned.. The script location is fixed regardless of where the user runs the command from, while getcwd() depends on the user's terminal state. Neither option affects speed or race conditions significantly.

    From lesson: os and pathlib Modules

  273. What happens if you call 'Path('data/logs').mkdir()' and the directory already exists?

    • It silently does nothing.
    • It overwrites the existing directory.
    • It raises a FileExistsError.
    • It merges the new files into the existing folder.

    Answer: It raises a FileExistsError.. By default, mkdir() raises a FileExistsError if the target exists. Option 1 would be true if exist_ok=True was passed. Options 2 and 4 are incorrect behaviors for creating a directory.

    From lesson: os and pathlib Modules

  274. You want to find all '.jpg' files in a directory recursively. Which method is most efficient?

    • Using os.listdir() and checking if each file ends with '.jpg'.
    • Using pathlib.Path.glob('**/*.jpg').
    • Using os.walk() and iterating through every file, checking the extension.
    • Using Path.iterdir() and an if-statement.

    Answer: Using pathlib.Path.glob('**/*.jpg').. glob('**/*.jpg') is designed specifically for recursive pattern matching. os.walk() and listdir() require significantly more manual boilerplate code to achieve the same result.

    From lesson: os and pathlib Modules

  275. Which statement best describes the return value of 'pathlib.Path('file.txt').resolve()'?

    • It returns a string representing the absolute path.
    • It returns a Path object pointing to the absolute, symlink-resolved path.
    • It returns the size of the file in bytes.
    • It returns a boolean indicating if the file exists.

    Answer: It returns a Path object pointing to the absolute, symlink-resolved path.. resolve() returns an absolute Path object, expanding symlinks and '..' segments. It does not return a string (that would be str(path)), nor a size, nor a boolean.

    From lesson: os and pathlib Modules

  276. If you run 'python script.py arg1', what is the value of len(sys.argv)?

    • 0
    • 1
    • 2
    • 3

    Answer: 2. The list sys.argv always contains the script name at index 0 and the arguments following it. Thus, 'script.py' and 'arg1' result in a length of 2. 0 and 1 are too small, while 3 would imply an extra argument was provided.

    From lesson: sys Module

  277. Which method should you use to modify the list of directories Python searches for modules during import?

    • sys.modules.append()
    • sys.path.append()
    • sys.argv.append()
    • sys.flags.append()

    Answer: sys.path.append(). sys.path is a list of strings representing search paths for modules. Modifying it affects imports. sys.modules is a dictionary of loaded modules, sys.argv holds command-line arguments, and sys.flags holds interpreter status; none of these manage import paths.

    From lesson: sys Module

  278. What is the primary difference between sys.exit() and simply letting a script finish naturally?

    • sys.exit() deletes all variables immediately
    • sys.exit() raises a SystemExit exception that can be intercepted
    • sys.exit() clears the system cache
    • There is no difference in behavior

    Answer: sys.exit() raises a SystemExit exception that can be intercepted. sys.exit() raises the SystemExit exception, allowing cleanup code in 'finally' blocks to run. It does not delete variables immediately, it is not a cache-clearing function, and there is a significant difference because normal termination happens without an exception.

    From lesson: sys Module

  279. How do you direct error messages specifically to the standard error stream instead of standard output?

    • Use print(msg, file=sys.stderr)
    • Use sys.stdout.write(msg)
    • Use sys.stdin.write(msg)
    • Use print(msg, stream='stderr')

    Answer: Use print(msg, file=sys.stderr). The print function accepts a 'file' argument which defaults to sys.stdout; setting it to sys.stderr redirects the output. sys.stdout.write() writes to standard output, sys.stdin is for input only, and the 'stream' argument does not exist for print.

    From lesson: sys Module

  280. What happens if you modify the sys.modules dictionary in a running program?

    • The script will immediately crash
    • Python will force a reboot of the interpreter
    • Future imports of the modified module will use the new object or fail
    • It has no effect on the interpreter

    Answer: Future imports of the modified module will use the new object or fail. sys.modules caches imported modules. Changing it influences subsequent import statements, which is a common (though risky) technique for mocking. It does not force a crash or reboot, and it certainly has an effect on the import system.

    From lesson: sys Module

  281. If you have a naive datetime object 'd' and you want to ensure it is treated as UTC, what is the safest practice?

    • d.replace(tzinfo=timezone.utc)
    • d.astimezone(timezone.utc)
    • d.isoformat(timezone.utc)
    • d.convert(timezone.utc)

    Answer: d.replace(tzinfo=timezone.utc). Using replace attaches the UTC timezone info without changing the wall-clock time. 'astimezone' is for conversion, 'isoformat' is for string output, and 'convert' does not exist in the library.

    From lesson: datetime and time Modules

  282. What is the primary difference between a 'timedelta' and a 'date' object?

    • Timedelta represents a specific point in time, while date represents an interval.
    • Timedelta represents the duration between two dates, while date represents a specific calendar day.
    • Date objects are mutable, while timedelta objects are immutable.
    • Timedelta includes microsecond precision, while date does not support hours or minutes.

    Answer: Timedelta represents the duration between two dates, while date represents a specific calendar day.. A 'date' object stores a specific year, month, and day. 'timedelta' represents a span or difference, which is the correct definition. The other options either describe them incorrectly or swap their functionality.

    From lesson: datetime and time Modules

  283. When measuring the execution time of a code block for performance profiling, why is 'time.perf_counter()' preferred over 'time.time()'?

    • It provides higher resolution and is unaffected by system clock updates.
    • It returns a datetime object which is easier to log.
    • It automatically calculates the average over multiple runs.
    • It is faster to execute than time.time().

    Answer: It provides higher resolution and is unaffected by system clock updates.. perf_counter is a monotonic clock specifically designed for performance measurement, ensuring stability against system time adjustments. 'time.time()' is wall-clock time, which can drift, and the other options are technically incorrect.

    From lesson: datetime and time Modules

  284. What happens when you subtract two datetime objects from each other?

    • A float representing the difference in seconds.
    • A new datetime object representing the earlier time.
    • A timedelta object representing the duration between them.
    • A tuple containing the difference in days and seconds.

    Answer: A timedelta object representing the duration between them.. Subtracting two datetime objects naturally results in a timedelta object. Returning a float or tuple would require manual conversion, and a new datetime object is logically incorrect for a difference operation.

    From lesson: datetime and time Modules

  285. Why should you avoid using 'datetime.now()' when dealing with cross-regional server applications?

    • It is deprecated in Python 3.10+.
    • It returns a naive object that defaults to the local system time of the server.
    • It does not support formatting into strings.
    • It requires a database connection to function correctly.

    Answer: It returns a naive object that defaults to the local system time of the server.. The 'now()' method returns a naive object tied to the host's OS time, which causes ambiguity in distributed systems. It is not deprecated, it supports string formatting, and it does not require a database.

    From lesson: datetime and time Modules

  286. You need to store a sequence of events where you frequently remove items from the beginning. Which collection is most efficient?

    • list
    • deque
    • tuple
    • set

    Answer: deque. A deque is designed for O(1) operations at both ends, making it perfect for queues. Lists have O(n) performance for removals from the start, tuples are immutable, and sets do not maintain order or allow duplicates.

    From lesson: collections — Counter, defaultdict, deque, namedtuple

  287. What happens when you access a missing key in a defaultdict(list)?

    • Raises a KeyError
    • Returns None
    • Creates a new empty list at that key
    • Returns an empty dictionary

    Answer: Creates a new empty list at that key. The list factory function is executed upon accessing a missing key, automatically assigning an empty list to that key. KeyError would occur in a standard dict; None or an empty dict are not the results of a list factory.

    From lesson: collections — Counter, defaultdict, deque, namedtuple

  288. Which of the following is true regarding namedtuple instances?

    • They are mutable and have high memory overhead
    • They are immutable and look like regular tuples with named access
    • They can be modified using dot notation like objects
    • They provide O(1) lookup speed for arbitrary string keys

    Answer: They are immutable and look like regular tuples with named access. namedtuples are immutable objects that allow access to elements by field name, providing readability without the overhead of a full class. They are not mutable, cannot be modified via dot notation, and are not designed for arbitrary dictionary-style key lookup.

    From lesson: collections — Counter, defaultdict, deque, namedtuple

  289. What is the result of 'Counter('aabbc') - Counter('abc')'?

    • Counter({'a': 1, 'b': 1})
    • Counter({'a': 2, 'b': 2, 'c': 1})
    • Counter({'a': 0, 'b': 0, 'c': 0})
    • Counter({'a': 1, 'b': 1, 'c': 0})

    Answer: Counter({'a': 1, 'b': 1}). Counter subtraction keeps only counts greater than zero. 'aabbc' minus 'abc' results in 'a': 1 and 'b': 1. The other options either fail to account for the subtraction logic or include zero/negative counts which are excluded.

    From lesson: collections — Counter, defaultdict, deque, namedtuple

  290. You have a list of items and want to count occurrences. Why choose Counter over a standard dict loop?

    • Counter is always faster for any size data
    • Counter provides convenience methods like most_common()
    • Counter requires less memory than a dictionary
    • Counter is the only way to track occurrences in Python

    Answer: Counter provides convenience methods like most_common(). Counter is built specifically for tallying and provides helper methods like most_common() and arithmetic operations. It is not necessarily faster for all cases or more memory-efficient than a dict; it is simply more specialized.

    From lesson: collections — Counter, defaultdict, deque, namedtuple

  291. What is the primary benefit of using itertools.chain() over using the + operator to join three large lists?

    • It performs the operation faster because it uses optimized C code.
    • It avoids creating a new intermediate list in memory by lazily yielding elements.
    • It automatically sorts the resulting elements while joining them.
    • It removes duplicate elements during the chaining process.

    Answer: It avoids creating a new intermediate list in memory by lazily yielding elements.. chain() returns an iterator that yields elements one by one, whereas + creates a brand new list in memory containing all elements from the inputs. The other options are incorrect because chain does not sort, does not deduplicate, and memory efficiency is its specific design goal, not just raw execution speed.

    From lesson: itertools — chain, product, combinations

  292. You have a list of lists: data = [[1, 2], [3, 4]]. Which approach correctly yields all individual integers using itertools?

    • itertools.chain(data)
    • itertools.chain(*data)
    • itertools.product(data)
    • itertools.combinations(data, 2)

    Answer: itertools.chain(*data). Using the unpacking operator (*) passes the sub-lists as separate arguments to chain(), which is the correct way to link them. chain(data) would treat the outer list as one object and fail to unpack the inner lists. product and combinations perform different mathematical operations entirely.

    From lesson: itertools — chain, product, combinations

  293. If you use itertools.product('AB', repeat=2), what is the resulting output sequence?

    • ('A', 'A'), ('B', 'B')
    • ('A', 'A'), ('A', 'B'), ('B', 'A'), ('B', 'B')
    • ('A', 'B'), ('B', 'A')
    • ('A', 'A'), ('A', 'B'), ('B', 'B')

    Answer: ('A', 'A'), ('A', 'B'), ('B', 'A'), ('B', 'B'). product with repeat=2 generates the Cartesian product of the set with itself. This includes all combinations of the items, including self-pairs. Option 1 is combinations, option 3 is permutations, and option 4 is incomplete.

    From lesson: itertools — chain, product, combinations

  294. How does itertools.combinations('ABC', 2) behave compared to itertools.permutations('ABC', 2)?

    • Combinations include ('A', 'B') and ('B', 'A'), while permutations only include ('A', 'B').
    • Combinations are sorted by index; permutations are not.
    • Combinations treats ('A', 'B') and ('B', 'A') as the same pair, while permutations treats them as distinct.
    • Combinations creates tuples, while permutations creates lists.

    Answer: Combinations treats ('A', 'B') and ('B', 'A') as the same pair, while permutations treats them as distinct.. Combinations represent unordered subsets, meaning order does not matter (A,B is the same as B,A). Permutations represent ordered arrangements, where order is significant. The other options misstate the fundamental mathematical definitions of these functions.

    From lesson: itertools — chain, product, combinations

  295. Why might you prefer itertools.chain.from_iterable(list_of_lists) over itertools.chain(*list_of_lists)?

    • It is faster because it does not require unpacking the entire list into function arguments.
    • It allows for the inclusion of non-iterable objects in the list.
    • It automatically removes empty iterables from the input.
    • It creates a list object instead of an iterator, preventing exhaustion.

    Answer: It is faster because it does not require unpacking the entire list into function arguments.. from_iterable is memory-efficient because it avoids the overhead of unpacking a potentially massive list into function arguments via the * operator. None of the other options are true: it does not handle non-iterables, it does not filter empty items, and it still returns an iterator.

    From lesson: itertools — chain, product, combinations

  296. Which of the following describes the behavior of json.dumps() when passed a Python dictionary containing a set?

    • It successfully serializes the set into a JSON array.
    • It raises a TypeError because sets are not natively JSON serializable.
    • It converts the set into a string representation like '{1, 2, 3}'.
    • It silently ignores the set and removes the key from the output.

    Answer: It raises a TypeError because sets are not natively JSON serializable.. The json module does not support sets. Option 0 and 2 are incorrect because there is no built-in conversion for sets, and option 3 is incorrect because the library raises an error rather than ignoring data.

    From lesson: json — Serialization and Deserialization

  297. When working with a file object, what is the correct approach to write a Python list to a JSON file named 'data.json'?

    • json.write(my_list, 'data.json')
    • json.dumps(my_list, file='data.json')
    • json.dump(my_list, open('data.json', 'w'))
    • json.dump(my_list, 'data.json')

    Answer: json.dump(my_list, open('data.json', 'w')). json.dump() requires a file-like object as the second argument. Option 0 does not exist, option 1 uses the wrong function (dumps is for strings), and option 3 fails because 'data.json' is a string, not an open file stream.

    From lesson: json — Serialization and Deserialization

  298. What is the primary difference between json.load() and json.loads()?

    • json.load() is for writing data, while json.loads() is for reading.
    • json.load() reads from a file object, while json.loads() parses a JSON-formatted string.
    • json.load() supports complex Python objects, while json.loads() only supports primitives.
    • There is no difference; they are aliases for the same function.

    Answer: json.load() reads from a file object, while json.loads() parses a JSON-formatted string.. json.load() is designed to read from a file handle (stream), whereas json.loads() (load-string) parses a string. The other options misrepresent the functionality of the two methods.

    From lesson: json — Serialization and Deserialization

  299. If you have a Python dictionary with a datetime object, what happens if you try to serialize it using json.dumps()?

    • It automatically formats the date as an ISO 8601 string.
    • It raises a TypeError because datetime objects are not JSON serializable.
    • It serializes it as a nested dictionary of its components.
    • It treats the datetime object as None.

    Answer: It raises a TypeError because datetime objects are not JSON serializable.. Datetime objects are not part of the basic types supported by the JSON standard, so Python will raise a TypeError. It does not auto-format, auto-convert, or cast to None.

    From lesson: json — Serialization and Deserialization

  300. You have a JSON string with a key using single quotes. What happens when you call json.loads() on it?

    • It succeeds because Python is flexible with quotes.
    • It fails with a JSONDecodeError because JSON requires double quotes.
    • It automatically corrects the single quotes to double quotes.
    • It treats the entire string as a single value rather than an object.

    Answer: It fails with a JSONDecodeError because JSON requires double quotes.. JSON standards strictly mandate double quotes for keys and strings. The json module enforces this strictly, so using single quotes will result in a JSONDecodeError. The other options incorrectly imply that the parser is permissive or auto-correcting.

    From lesson: json — Serialization and Deserialization

  301. Which of the following describes the difference between re.search() and re.match() in Python?

    • re.search() finds the first location, while re.match() returns all occurrences
    • re.search() checks anywhere in the string, while re.match() only checks from the beginning
    • re.search() is used for strings, while re.match() is used for byte objects
    • There is no functional difference between the two methods

    Answer: re.search() checks anywhere in the string, while re.match() only checks from the beginning. Option 2 is correct because re.match() anchors the search to the start of the string, whereas re.search() scans the entire string. Option 1 is wrong because findall() returns all occurrences. Option 3 is wrong as both support strings and bytes. Option 4 is incorrect because their behaviors are distinct.

    From lesson: re — Regular Expressions

  302. Given the pattern r'a+b', what will re.search(r'a+b', 'aaaab').group() return?

    • 'ab'
    • 'a'
    • 'aaaab'
    • 'aaab'

    Answer: 'aaaab'. Option 3 is correct because '+' is a greedy quantifier that matches one or more repetitions of 'a'. Options 1, 2, and 4 are wrong because the quantifier consumes all 'a' characters available before the 'b'.

    From lesson: re — Regular Expressions

  303. If you want to make a quantifier non-greedy, what character do you append to it?

    • !
    • +
    • ?
    • $

    Answer: ?. Option 3 is correct; appending '?' makes greedy quantifiers (*, +, {n,}) non-greedy. Option 1, 2, and 4 are invalid syntax for changing quantifier behavior in Python regex.

    From lesson: re — Regular Expressions

  304. What is the result of using re.findall(r'(\d+)-(\d+)', '123-456 789-012')?

    • [('123', '456'), ('789', '012')]
    • ['123-456', '789-012']
    • ['123', '456', '789', '012']
    • None

    Answer: [('123', '456'), ('789', '012')]. Option 1 is correct because when capture groups are present in findall(), it returns a list of tuples containing the captured groups. Option 2 would be correct if there were no parentheses. Option 3 is wrong because it ignores the tuple structure. Option 4 is wrong because the pattern matches successfully.

    From lesson: re — Regular Expressions

  305. Why is it recommended to use r'...' (raw string notation) for regex patterns?

    • It makes the regex execute faster
    • It allows the regex to match Unicode characters
    • It prevents Python from interpreting backslashes as escape characters
    • It is required to access group objects

    Answer: It prevents Python from interpreting backslashes as escape characters. Option 3 is correct because raw strings treat backslashes literally, which is crucial for regex tokens like \d or \s. Options 1, 2, and 4 are incorrect because raw strings do not impact performance, Unicode handling, or group access.

    From lesson: re — Regular Expressions

  306. What is the primary reason for using logger.info('User %s logged in', username) instead of logger.info(f'User {username} logged in')?

    • It prevents syntax errors in older Python versions.
    • It avoids performing string formatting if the logging level is currently disabled.
    • It automatically adds timestamps to the message.
    • It ensures the username is always converted to a string.

    Answer: It avoids performing string formatting if the logging level is currently disabled.. The second option is correct because the logging module performs the formatting lazily only if the log record is actually emitted. The other options are incorrect because f-strings are supported in modern Python, timestamps are handled by the Formatter, and str() conversion happens in both cases.

    From lesson: logging Module

  307. If you are writing a reusable library, what is the best practice for initializing a logger?

    • Call logging.basicConfig() at the module level.
    • Use logging.getLogger('root').
    • Use logger = logging.getLogger(__name__).
    • Assign a custom StreamHandler to the base logger.

    Answer: Use logger = logging.getLogger(__name__).. Using __name__ ensures that the logger follows the package hierarchy, allowing developers to configure it easily. The other options involve configuring global state, which should only be done by the top-level application, not libraries.

    From lesson: logging Module

  308. When an exception occurs, why is logger.exception('An error occurred') preferred over logger.error('An error occurred: ' + str(e))?

    • It is faster to execute.
    • It automatically captures and appends the full traceback information.
    • It changes the logging level to CRITICAL automatically.
    • It disables all other loggers to isolate the error.

    Answer: It automatically captures and appends the full traceback information.. logger.exception is specifically designed to be used in except blocks to capture the stack trace. The other options are incorrect because speed is negligible here, it does not change the level, and it does not affect other loggers.

    From lesson: logging Module

  309. What happens if you do not define a handler for a logger, but the root logger has one defined?

    • The log message is lost.
    • The log message is sent to the handlers of the parent loggers (propagation).
    • The logger throws an unhandled exception.
    • The log message is printed to standard error only if it is a CRITICAL level.

    Answer: The log message is sent to the handlers of the parent loggers (propagation).. Loggers propagate messages to their parents by default. The other options are false because the message is successfully handled by the root logger, no exception is raised, and it works for all levels depending on the root's level setting.

    From lesson: logging Module

  310. How can you ensure that different parts of your application output logs to different files simultaneously?

    • By calling logging.basicConfig() multiple times.
    • By attaching different FileHandler instances to different named loggers.
    • By setting the log level to different values for each file.
    • By using multiple threads to manage a single global file object.

    Answer: By attaching different FileHandler instances to different named loggers.. Attaching unique handlers to specific loggers allows for fine-grained control over destinations. basicConfig only works once, setting levels doesn't change destinations, and multiple threads on one file requires locking which the logging module handles internally via handlers.

    From lesson: logging Module

  311. When defining an argument, why should you use type=int?

    • It validates that the input is a valid integer and converts it from a string.
    • It forces the user to input the number as a decimal value.
    • It tells the terminal to only accept numerical input keys.
    • It automatically rounds the input to the nearest integer.

    Answer: It validates that the input is a valid integer and converts it from a string.. Option 0 is correct because type=int converts the string input to an integer type. Option 1 is wrong because integers are whole numbers. Option 2 is wrong because the terminal always passes data as strings. Option 3 is wrong because argparse handles logic, not terminal input restrictions.

    From lesson: argparse — CLI Arguments

  312. What is the primary difference between a positional argument and an optional argument?

    • Positional arguments can only be used if they start with a dash.
    • Optional arguments are always required for the script to execute.
    • Positional arguments are determined by their order, while optional arguments use flags.
    • Optional arguments are defined using the add_positional method.

    Answer: Positional arguments are determined by their order, while optional arguments use flags.. Option 2 is correct because positional arguments rely on their sequence, and flags are used for optional ones. Option 0 is wrong because positional arguments do not use dashes. Option 1 is wrong because optional arguments are, by definition, not required. Option 3 is wrong because there is no add_positional method.

    From lesson: argparse — CLI Arguments

  313. How do you create a flag that behaves as a True/False toggle in a script?

    • Use action='store_value'.
    • Use action='store_true'.
    • Set default=False and type=bool.
    • Define it as a positional argument without a dash.

    Answer: Use action='store_true'.. Option 1 is correct because store_true automatically assigns True if the flag is present and False otherwise. Option 0 is not a valid action. Option 2 is wrong because type=bool does not parse 'False' correctly from CLI strings. Option 3 is wrong because toggles must be optional flags.

    From lesson: argparse — CLI Arguments

  314. If you define an argument with '--input' and the user provides the value 'data.txt', how is this accessed in the object returned by parse_args()?

    • args.input
    • args['--input']
    • args.data.txt
    • args.args.input

    Answer: args.input. Option 0 is correct; argparse strips the dashes and uses the name of the argument as an attribute. Option 1 is wrong because it uses dot notation, not dictionary bracket notation. Option 2 is wrong because that is the value, not the name. Option 3 is wrong because the object is returned directly, not nested.

    From lesson: argparse — CLI Arguments

  315. What happens if a user passes a required positional argument that is not defined in the script?

    • The script ignores the extra argument automatically.
    • The script crashes with a SyntaxError.
    • The parser displays an error message and terminates the program.
    • The variable for that argument is set to None.

    Answer: The parser displays an error message and terminates the program.. Option 2 is correct because argparse includes built-in error handling for missing arguments. Option 0 is wrong because unparsed arguments generally trigger an error. Option 1 is wrong because it is not a syntax error. Option 3 is wrong because missing required arguments do not allow the script to proceed.

    From lesson: argparse — CLI Arguments

  316. Given a list 'a = [[1, 2], [3, 4]]' and 'b = a.copy()', what happens if you execute 'b[0][0] = 9'?

    • Only b[0][0] becomes 9
    • Both a[0][0] and b[0][0] become 9
    • An error is raised because a is a nested list
    • Only a[0][0] becomes 9

    Answer: Both a[0][0] and b[0][0] become 9. A shallow copy creates a new outer list but shares the references to the nested lists. Because they point to the same list objects, modifying 'b' affects 'a'. The other options are incorrect because the outer structure is copied, but the inner elements are not.

    From lesson: copy — Shallow vs Deep Copy

  317. Which of the following scenarios absolutely requires a deep copy to ensure complete independence?

    • Copying a list of integers
    • Copying a list of strings
    • Copying a dictionary containing lists as values
    • Copying a tuple of immutable integers

    Answer: Copying a dictionary containing lists as values. Lists are mutable. In a dictionary of lists, a shallow copy duplicates the dictionary, but the nested lists remain shared. Integers, strings, and tuples of integers are immutable, so sharing references is safe and functionally identical to a deep copy.

    From lesson: copy — Shallow vs Deep Copy

  318. What is the result of using 'list(original_list)' compared to a shallow copy?

    • It performs a deep copy
    • It results in the exact same shallow copy behavior
    • It creates a reference, not a copy
    • It causes an error if the list contains objects

    Answer: It results in the exact same shallow copy behavior. Both the list constructor and .copy() create a new list object with references to the original items, making them both shallow copies. It is not a deep copy, nor is it merely a reference.

    From lesson: copy — Shallow vs Deep Copy

  319. If you define a custom class 'Data' and use 'copy.deepcopy(instance)', what is required for it to work?

    • The class must inherit from a special copyable base class
    • The class must implement a __deepcopy__ method
    • By default, Python handles custom objects by recursively copying their __dict__
    • It will fail because custom objects cannot be deep copied

    Answer: By default, Python handles custom objects by recursively copying their __dict__. The copy module's deepcopy function is designed to recursively traverse the object's dictionary and copy attributes. The other options are incorrect because no special inheritance is needed and it does not fail by default.

    From lesson: copy — Shallow vs Deep Copy

  320. Consider 'x = [1, 2, 3]'. If you perform 'y = x[:]', which statement is true?

    • x and y occupy the same memory address
    • y is a new list object, but contains the same integer references as x
    • y is a deep copy of x
    • Modifying x will affect y since they share memory

    Answer: y is a new list object, but contains the same integer references as x. Slicing with [:] creates a new list (a shallow copy). Since integers are immutable in Python, sharing the references is perfectly safe and efficient. Option 0 is false as they are different objects; option 2 is false as it's only shallow; option 3 is false because integers cannot be modified in place.

    From lesson: copy — Shallow vs Deep Copy

  321. What is the primary difference between NumPy arrays and standard Python lists when performing arithmetic?

    • Lists support vectorized operations natively while arrays do not
    • Arrays perform element-wise arithmetic, whereas lists concatenate or repeat
    • Arrays automatically resize, while lists require manual memory management
    • Lists use less memory than arrays due to dynamic typing

    Answer: Arrays perform element-wise arithmetic, whereas lists concatenate or repeat. Arrays perform element-wise arithmetic (e.g., [1,2]*2 results in [2,4]), while list multiplication repeats the list content. The other options are either false or attribute benefits to lists that actually belong to arrays.

    From lesson: NumPy — Arrays and Operations

  322. If you slice an array as 'new_arr = arr[0:2]', what happens when you perform 'new_arr[0] = 99'?

    • The original 'arr' remains unchanged
    • A TypeError occurs because slices are immutable
    • The original 'arr' is updated because the slice is a view
    • The program crashes due to memory overflow

    Answer: The original 'arr' is updated because the slice is a view. NumPy slices create views, meaning they point to the original memory buffer. Therefore, modifying the view updates the original array. Options 1 and 4 are incorrect because views are mutable; option 2 is incorrect as NumPy objects are generally mutable.

    From lesson: NumPy — Arrays and Operations

  323. When computing a dot product of two 2D arrays, why should you prefer the '@' operator over the '*' operator?

    • The '@' operator is faster because it bypasses memory checks
    • The '@' operator performs matrix multiplication, while '*' performs element-wise multiplication
    • The '*' operator does not support broadcasting
    • The '@' operator only works on integers

    Answer: The '@' operator performs matrix multiplication, while '*' performs element-wise multiplication. In NumPy, '@' is specifically implemented for matrix multiplication (dot product), whereas '*' is the standard Hadamard/element-wise product. Option 3 is false as '*' supports broadcasting; option 4 is false as both work with floats.

    From lesson: NumPy — Arrays and Operations

  324. What happens if you try to perform 'arr + 5' where 'arr' is a NumPy array?

    • An error is raised because you cannot add an integer to an array
    • The number 5 is appended to the end of the array
    • The value 5 is added to every individual element in the array
    • Only the first element of the array is increased by 5

    Answer: The value 5 is added to every individual element in the array. NumPy uses broadcasting to apply scalar operations to every element in the array simultaneously. It does not raise an error, nor does it append or limit the operation to the first element.

    From lesson: NumPy — Arrays and Operations

  325. Given an array of shape (3, 4), what is the shape after calling 'arr.sum(axis=0)'?

    • (3,)
    • (4,)
    • (1, 4)
    • (3, 1)

    Answer: (4,). When axis=0 is specified, the sum operation collapses the rows (the first dimension), leaving the number of columns unchanged, resulting in a (4,) array. Other options represent incorrect dimensional reduction.

    From lesson: NumPy — Arrays and Operations

  326. Given a DataFrame 'df', what is the result of 'df[['A', 'B']]'?

    • A Series containing column A and B
    • A DataFrame containing column A and B
    • A TypeError because list indexing is not supported
    • A list of the first two rows

    Answer: A DataFrame containing column A and B. Passing a list of strings to the selection operator returns a DataFrame containing those columns. The first option is wrong because a single column selection returns a Series, but multiple columns return a DataFrame. The third is wrong because list indexing is supported. The fourth is wrong because strings refer to columns, not rows.

    From lesson: Pandas — DataFrames and Series

  327. How does Pandas handle missing values during an arithmetic operation like 'Series_A + Series_B'?

    • It treats missing values as zero
    • It raises a ValueError
    • It results in NaN at the corresponding index
    • It removes the rows with missing values from the result

    Answer: It results in NaN at the corresponding index. Pandas propagates NaN for missing values in arithmetic operations. It doesn't treat them as zero by default, nor does it raise an error or drop rows automatically. You must use fillna() if you want to treat them as zero.

    From lesson: Pandas — DataFrames and Series

  328. What is the primary difference between a Series and a DataFrame?

    • Series is 1D and DataFrame is 2D
    • Series is for numbers and DataFrame is for strings
    • Series can have an index but DataFrames cannot
    • Series is faster than DataFrames

    Answer: Series is 1D and DataFrame is 2D. A Series is a one-dimensional array-like object, while a DataFrame is a two-dimensional, tabular data structure. Both can hold any data type and both have indices. Speed depends on the operation, not the data structure type itself.

    From lesson: Pandas — DataFrames and Series

  329. What does the 'inplace=True' parameter in many Pandas methods achieve?

    • It makes the operation faster by skipping memory allocation
    • It returns a copy of the object rather than modifying it
    • It modifies the original object and returns None
    • It converts the object to a NumPy array

    Answer: It modifies the original object and returns None. The 'inplace=True' parameter tells Pandas to apply the operation directly to the existing object instead of returning a modified copy. It returns None because the operation is performed in memory on the original object. It does not improve speed or convert data to NumPy arrays.

    From lesson: Pandas — DataFrames and Series

  330. When you perform a boolean selection like 'df[df['col'] > 5]', what is actually happening?

    • It converts the column to a list of integers
    • It returns a Series of boolean values
    • It filters the DataFrame to rows where the condition is True
    • It re-indexes the DataFrame based on the condition

    Answer: It filters the DataFrame to rows where the condition is True. This is boolean indexing; Pandas evaluates the condition for every row and returns a DataFrame containing only rows where the condition evaluated to True. The second option describes the result of just 'df['col'] > 5', not the whole expression.

    From lesson: Pandas — DataFrames and Series

  331. You need to send a dictionary as a JSON body to an API endpoint. Which parameter should you use and why?

    • The 'data' parameter, because it is the standard way to send dictionaries.
    • The 'json' parameter, because it automatically serializes the dict and sets the correct Content-Type header.
    • The 'params' parameter, because it converts the dictionary into JSON string format.
    • The 'headers' parameter, because it is required to define the payload type.

    Answer: The 'json' parameter, because it automatically serializes the dict and sets the correct Content-Type header.. The 'json' parameter is designed specifically for API payloads. 'data' is for form-encoding, 'params' is for URL query strings, and 'headers' is for metadata, not the body itself.

    From lesson: Requests — HTTP Calls

  332. What is the primary benefit of using 'requests.Session()' over making individual calls with 'requests.get()'?

    • It prevents the server from rate-limiting your requests.
    • It automatically parses the response body into a Python dictionary.
    • It allows for connection pooling, reusing underlying TCP connections.
    • It makes the HTTP response status code check optional.

    Answer: It allows for connection pooling, reusing underlying TCP connections.. Session objects enable connection pooling. Options 1 and 2 are incorrect because sessions don't affect rate limits or auto-parsing, and option 4 is incorrect because session usage does not change error handling requirements.

    From lesson: Requests — HTTP Calls

  333. A request returns a 404 Not Found status. What will the following code do: 'response = requests.get(url); response.raise_for_status()'?

    • It will return None and continue the script execution.
    • It will print an error message but not halt the program.
    • It will raise an HTTPError exception.
    • It will automatically attempt to retry the request three times.

    Answer: It will raise an HTTPError exception.. raise_for_status() triggers an exception for 4xx and 5xx responses. It does not return None, print errors, or retry automatically.

    From lesson: Requests — HTTP Calls

  334. Which approach is most robust for handling a server that might be temporarily unreachable?

    • Checking if response.status_code is 200.
    • Using a try-except block catching requests.exceptions.RequestException.
    • Setting the 'verify' parameter to False.
    • Adding a long 'timeout' value to the request call only.

    Answer: Using a try-except block catching requests.exceptions.RequestException.. Exceptions are raised during the connection phase, before a status code is even received. 'verify' relates to SSL, and a timeout alone doesn't handle the exception, it only triggers it.

    From lesson: Requests — HTTP Calls

  335. If you want to filter a list of products by a category via a GET request, where should the category information be placed?

    • In the 'params' dictionary.
    • In the 'data' dictionary.
    • In the 'json' dictionary.
    • In the 'cookies' dictionary.

    Answer: In the 'params' dictionary.. GET requests traditionally use URL query strings for filtering, which are handled by the 'params' parameter. 'data' and 'json' are for body payloads, and 'cookies' are for session tracking.

    From lesson: Requests — HTTP Calls

  336. If you are using the object-oriented approach with 'fig, ax = plt.subplots()', which command correctly adds a title to the specific plot area?

    • plt.title('My Title')
    • ax.set_title('My Title')
    • fig.title('My Title')
    • plt.set_title('My Title')

    Answer: ax.set_title('My Title'). ax.set_title() is the correct method for an Axes object. Option 0 modifies the global state, which is discouraged. Option 2 is invalid because Figures do not have a set_title method. Option 3 is a non-existent method.

    From lesson: Matplotlib — Basic Plotting

  337. What happens if you provide only one list to the plt.plot() function?

    • It generates an error because two lists are required.
    • It treats the list as y-values and uses the indices of the list as x-values.
    • It plots the list against itself, resulting in a diagonal line.
    • It waits for a second list via user input.

    Answer: It treats the list as y-values and uses the indices of the list as x-values.. Matplotlib defaults to using the list indices (0, 1, 2...) as x-values if only one argument is provided. It does not error (0), does not plot against itself (2), and does not pause for input (3).

    From lesson: Matplotlib — Basic Plotting

  338. Why is it often preferred to use 'ax.plot()' instead of 'plt.plot()'?

    • It is significantly faster to render.
    • It allows for better control when managing multiple subplots within a single figure.
    • It uses less memory than the state-based approach.
    • It is the only way to plot non-numeric data.

    Answer: It allows for better control when managing multiple subplots within a single figure.. Using Axes objects (ax) allows for explicit referencing, which is necessary when working with multiple subplots. Performance (0) and memory (2) are identical, and both approaches handle the same data types (3).

    From lesson: Matplotlib — Basic Plotting

  339. If you want to customize the line style, color, and marker in a single call to ax.plot(), what is the most concise way?

    • Use individual setter methods for every property.
    • Pass a format string like 'r--o' as an argument.
    • You cannot customize these in one call.
    • Use a dictionary of styles as the first argument.

    Answer: Pass a format string like 'r--o' as an argument.. Matplotlib's format string shorthand (e.g., 'r--o' for red, dashed line, circle markers) is designed for efficiency. Option 0 is verbose, Option 2 is false, and Option 3 is incorrect syntax.

    From lesson: Matplotlib — Basic Plotting

  340. When plotting multiple datasets on the same axes, how do you ensure the viewer knows which line is which?

    • Change the background color of the plot.
    • Use the 'label' argument in ax.plot() and call ax.legend().
    • Add a comment in the source code.
    • Increase the thickness of the lines.

    Answer: Use the 'label' argument in ax.plot() and call ax.legend().. Assigning labels to each plot command and then calling the legend method is the standard way to create a reference key. Options 0, 2, and 3 do not provide an actual legend for the data series.

    From lesson: Matplotlib — Basic Plotting

  341. When defining a model, what is the primary purpose of the 'Column' type definition?

    • To execute a command that creates a table immediately
    • To define the data type and constraints for a database field
    • To validate data types at runtime in Python
    • To create a primary key automatically regardless of configuration

    Answer: To define the data type and constraints for a database field. Option 2 is correct because Column maps Python attributes to database schema definitions. Option 1 is wrong because table creation requires metadata.create_all(). Option 3 is wrong because Column does not enforce runtime type validation. Option 4 is wrong because primary keys must be explicitly marked.

    From lesson: SQLAlchemy Basics

  342. What is the result of session.add(my_object) in SQLAlchemy?

    • The object is immediately written to the database disk
    • The object is marked as 'pending' and added to the current session's identity map
    • The object is validated against the database schema constraints
    • The object is automatically committed to the database

    Answer: The object is marked as 'pending' and added to the current session's identity map. Option 2 is correct because add() transitions the object into the session state. Option 1 is wrong because commit() is required for persistence. Option 3 is wrong because validation usually happens upon flush or commit. Option 4 is wrong because SQLAlchemy follows an explicit commit pattern.

    From lesson: SQLAlchemy Basics

  343. Why would you choose to use 'joinedload' in a query?

    • To improve performance by reducing the number of queries for related objects
    • To force the database to merge two tables into a new physical table
    • To create a bidirectional relationship between two models
    • To delete related records automatically when a parent is deleted

    Answer: To improve performance by reducing the number of queries for related objects. Option 1 is correct as it solves the 'N+1' query problem. Option 2 is wrong because joinedload only affects the SELECT statement result. Option 3 is wrong because that is handled by the relationship() function. Option 4 is wrong because that is handled by the cascade setting.

    From lesson: SQLAlchemy Basics

  344. What happens if you execute session.rollback()?

    • It removes all records from the database table
    • It reverts the session to its state before the current transaction started
    • It deletes the Python objects currently in memory
    • It performs a clean shutdown of the database engine

    Answer: It reverts the session to its state before the current transaction started. Option 2 is correct because rollback undoes uncommitted changes. Option 1 is wrong because it only affects the transaction, not existing table data. Option 3 is wrong because objects remain in memory but their session state resets. Option 4 is wrong because it does not disconnect the engine.

    From lesson: SQLAlchemy Basics

  345. How does the 'relationship' function differ from a 'ForeignKey'?

    • ForeignKey creates the relationship, relationship is optional metadata
    • ForeignKey handles the database structure, relationship handles Python object navigation
    • They are identical and can be used interchangeably
    • Relationship is used for integers while ForeignKey is used for strings

    Answer: ForeignKey handles the database structure, relationship handles Python object navigation. Option 2 is correct because ForeignKeys are for database schema linking, while relationship() enables high-level ORM navigation. Option 1 is wrong because both are needed for a full ORM experience. Option 3 is wrong because they serve different layers. Option 4 is wrong because type definitions are not based on the function name.

    From lesson: SQLAlchemy Basics

  346. Why does FastAPI recommend using Pydantic models for request bodies instead of raw dictionaries?

    • It provides automatic data validation and serialization
    • It is required to run the internal web server
    • It increases the speed of the underlying C libraries
    • It forces the use of synchronous programming

    Answer: It provides automatic data validation and serialization. Pydantic provides automatic schema enforcement and error messages; raw dictionaries offer no structure, while the other options are either false or unrelated to the primary benefit of Pydantic.

    From lesson: FastAPI Quick Overview

  347. What is the primary difference between defining a route with 'def' versus 'async def' in FastAPI?

    • Only 'async def' can be used with Pydantic models
    • FastAPI runs 'def' routes in a separate thread pool and 'async def' on the event loop
    • Routes defined with 'def' cannot be accessed by browser clients
    • There is no difference in how FastAPI executes these functions

    Answer: FastAPI runs 'def' routes in a separate thread pool and 'async def' on the event loop. FastAPI handles 'def' routes in an external thread pool to prevent blocking the main event loop, whereas 'async def' routes run directly on the event loop. The other options are incorrect interpretations of the architecture.

    From lesson: FastAPI Quick Overview

  348. If you define a path operation as '/items/{id}' and another as '/items/recent', why might the latter be unreachable?

    • FastAPI prohibits nested path segments
    • FastAPI does not support static paths once dynamic ones are defined
    • The dynamic route matches 'recent' as an ID before the specific route is reached
    • Both routes require the same path prefix, which causes a syntax error

    Answer: The dynamic route matches 'recent' as an ID before the specific route is reached. Because FastAPI evaluates routes in order, the path parameter '{id}' matches the string 'recent'. The other options are false; FastAPI handles specific and dynamic routes fine if ordered correctly.

    From lesson: FastAPI Quick Overview

  349. When declaring a path parameter like 'item_id: int', what happens if a user sends '/items/abc'?

    • The function receives 'abc' as a string
    • The server crashes due to a type conversion error
    • FastAPI returns a 422 Unprocessable Entity error
    • The variable 'item_id' is set to None

    Answer: FastAPI returns a 422 Unprocessable Entity error. FastAPI automatically validates that the input matches the type hint; if it fails to cast to an int, it responds with an automated 422 error. The other options suggest manual handling that FastAPI automates.

    From lesson: FastAPI Quick Overview

  350. How does FastAPI determine whether a parameter is a query parameter or a path parameter?

    • Parameters matching a path string are path parameters, others are query parameters
    • All parameters without a default value are path parameters
    • Parameters must be manually registered in the FastAPI object
    • The order of arguments in the function definition decides

    Answer: Parameters matching a path string are path parameters, others are query parameters. If a function argument matches a variable in the path string, it is treated as a path parameter; if not, it is assumed to be a query parameter. The other options do not describe FastAPI's path-matching logic.

    From lesson: FastAPI Quick Overview

  351. What is the result of the following code? def func(a=[]): a.append(1); return a; print(func()); print(func())

    • [1], [1]
    • [1], [1, 1]
    • None, None
    • Error: default arguments must be immutable

    Answer: [1], [1, 1]. Because the default argument 'a' is initialized when the function is defined, the same list object is reused in subsequent calls. Option 0 and 2 are incorrect because the list is modified; option 3 is incorrect because lists are allowed as default arguments, though it is considered bad practice.

    From lesson: Python Interview Questions — Basics

  352. Which of the following correctly describes how Python handles variable scope when using the 'global' keyword?

    • It forces the variable to be accessible in all other modules.
    • It allows a function to modify a variable defined at the module level.
    • It makes the variable constant and immutable throughout the program.
    • It allows a variable to be defined inside a function and seen by its parent function.

    Answer: It allows a function to modify a variable defined at the module level.. The 'global' keyword allows a function to perform assignments to a variable defined in the module-level scope. Option 0 is wrong as it doesn't cross module boundaries; option 2 is wrong because 'global' doesn't enforce constants; option 3 describes 'nonlocal'.

    From lesson: Python Interview Questions — Basics

  353. What is the difference between shallow copy and deep copy in the context of the copy module?

    • Shallow copy is faster but uses more memory.
    • Deep copy copies the object and all objects found within it recursively.
    • Shallow copy creates a new object and populates it with references to the children of the original.
    • Both 1 and 2 are correct.

    Answer: Both 1 and 2 are correct.. A shallow copy creates a new container but stores references to the original child objects, while a deep copy recursively copies everything. Option 0 is false as copy speed depends on complexity; option 3 correctly identifies both fundamental definitions.

    From lesson: Python Interview Questions — Basics

  354. What is the primary behavior of the 'is' operator in Python?

    • It checks if two variables refer to the exact same object in memory.
    • It compares the values of two objects for equality.
    • It checks if an object is an instance of a specific class.
    • It performs a bitwise comparison between two integers.

    Answer: It checks if two variables refer to the exact same object in memory.. 'is' checks for identity, meaning the objects share the same memory address. '==' checks for value equality. Option 2 describes 'isinstance()', and option 3 is unrelated.

    From lesson: Python Interview Questions — Basics

  355. If you have a list 'nums = [1, 2, 3]', what happens if you run 'for x in nums: nums.remove(x)'?

    • It removes all items and leaves an empty list.
    • It raises a ValueError.
    • It removes only some items because the index pointer increments while the list shrinks.
    • The loop continues infinitely.

    Answer: It removes only some items because the index pointer increments while the list shrinks.. Modifying a list during iteration causes the loop to skip over elements because the indices shift. Option 0 is false because not all elements are processed; option 1 is false because the code is syntactically valid; option 3 is false as the loop will eventually exhaust.

    From lesson: Python Interview Questions — Basics

  356. What is the primary difference in performance between checking membership in a list vs. a set?

    • Lists are faster because they maintain order.
    • Sets use hashing to achieve O(1) average lookup, whereas lists use O(n) linear search.
    • There is no difference in performance between the two.
    • Lists are faster because they are stored contiguously in memory.

    Answer: Sets use hashing to achieve O(1) average lookup, whereas lists use O(n) linear search.. Sets are implemented as hash tables, making membership lookups O(1) on average. Lists must scan elements one by one, resulting in O(n). Order does not make list lookups faster, and contiguous memory helps iteration but not random lookup performance.

    From lesson: Python Interview Questions — Data Structures

  357. Given 'a = [1, 2, 3]' and 'b = a', what happens if you execute 'b.append(4)'?

    • Only b is updated to [1, 2, 3, 4].
    • Only a is updated to [1, 2, 3, 4].
    • Both a and b are updated to [1, 2, 3, 4].
    • A copy error occurs because the lists are linked.

    Answer: Both a and b are updated to [1, 2, 3, 4].. In Python, assigning a list to a new variable name creates a reference to the same object in memory, not a copy. Modifying the list through either name reflects the change globally for both variables. The other options ignore how object references behave.

    From lesson: Python Interview Questions — Data Structures

  358. What happens if you use a list as a dictionary key?

    • It works perfectly fine.
    • The list is automatically converted to a tuple.
    • It raises a TypeError because lists are unhashable.
    • It only works if the list is empty.

    Answer: It raises a TypeError because lists are unhashable.. Dictionary keys must be hashable, meaning they must have a stable hash value. Since lists are mutable, their contents can change, making them unhashable. The other options are incorrect because Python does not automatically mutate types to satisfy keys, and even an empty list is mutable.

    From lesson: Python Interview Questions — Data Structures

  359. Which of the following best describes the behavior of a Python tuple containing a list?

    • The tuple is entirely immutable and nothing inside it can change.
    • The tuple reference is immutable, but the contained list can be modified.
    • The tuple becomes mutable as soon as a list is inserted.
    • The list inside the tuple is automatically converted to a tuple.

    Answer: The tuple reference is immutable, but the contained list can be modified.. A tuple is a container of references. While you cannot reassign the index of the tuple to point to a different object, you can modify the objects themselves if they are mutable. The other options misinterpret how object references and tuple immutability work.

    From lesson: Python Interview Questions — Data Structures

  360. Why is it inefficient to concatenate strings using a loop and the '+' operator?

    • Strings are immutable, so every concatenation creates an entirely new string object.
    • The '+' operator has high overhead for small strings.
    • Memory allocation for strings is restricted to specific sizes.
    • It leads to memory leaks in all versions of Python.

    Answer: Strings are immutable, so every concatenation creates an entirely new string object.. Because strings are immutable, every '+' creates a new string object in memory, leading to O(n^2) complexity. Using ''.join() is efficient because it calculates the total size first. The other options are either false or do not explain the primary performance issue.

    From lesson: Python Interview Questions — Data Structures

  361. What happens when you define a method with a double underscore prefix (e.g., __method) in a Python class?

    • The method becomes completely inaccessible from outside the class.
    • The interpreter performs name mangling, renaming the attribute to _ClassName__method.
    • The method is automatically marked as static by the compiler.
    • The method is strictly private and will raise an AttributeError if called externally.

    Answer: The interpreter performs name mangling, renaming the attribute to _ClassName__method.. Option 1 is wrong because it can still be accessed via mangled names. Option 2 is correct as it describes how Python avoids collisions in subclasses. Option 3 is wrong because it has no effect on static status. Option 4 is wrong because Python does not enforce strict privacy.

    From lesson: Python Interview Questions — OOP

  362. In Python's Method Resolution Order (MRO), how does the language determine which method to call in a multiple inheritance scenario?

    • It uses a depth-first search, visiting the child, then the first parent, then the grandparent.
    • It uses the C3 Linearization algorithm to provide a consistent and predictable method ordering.
    • It prioritizes the method defined in the most recently inherited base class.
    • It defaults to the method found in the 'object' class if multiple parents define it.

    Answer: It uses the C3 Linearization algorithm to provide a consistent and predictable method ordering.. Option 1 describes the old-style class behavior. Option 2 is correct as C3 Linearization is the standard used for reliable MRO. Option 3 is wrong because MRO is fixed at class definition. Option 4 is wrong as it doesn't describe the order of inheritance.

    From lesson: Python Interview Questions — OOP

  363. When defining a class method, what is the primary purpose of the '@classmethod' decorator?

    • It allows the method to access instance variables without creating an object.
    • It changes the first argument from 'self' to 'cls', allowing access to class state.
    • It prevents the method from being overridden by subclasses.
    • It increases the execution speed of the method during object instantiation.

    Answer: It changes the first argument from 'self' to 'cls', allowing access to class state.. Option 1 is wrong because instance variables require an instance. Option 2 is correct because it passes the class itself. Option 3 is wrong as decorators don't restrict overriding. Option 4 is wrong as it is a architectural choice, not a performance optimization.

    From lesson: Python Interview Questions — OOP

  364. What is the primary difference between '__init__' and '__new__' in Python?

    • __init__ creates the object, while __new__ initializes the object's attributes.
    • __new__ is used for static methods, while __init__ is for instance methods.
    • __new__ is the constructor responsible for creating the instance, while __init__ initializes it.
    • __init__ can return an object, while __new__ must return None.

    Answer: __new__ is the constructor responsible for creating the instance, while __init__ initializes it.. Option 1 is backwards. Option 2 is incorrect. Option 3 is correct: __new__ creates the object, and __init__ sets the initial state. Option 4 is wrong because __new__ MUST return an instance, whereas __init__ returns None.

    From lesson: Python Interview Questions — OOP

  365. Why is it considered better practice to use properties (@property) rather than public attributes in Python?

    • Properties are automatically thread-safe, unlike public attributes.
    • Properties allow for the implementation of getters and setters without changing the external API.
    • Properties are required for any class to be compatible with built-in functions.
    • Properties are the only way to make an attribute read-only.

    Answer: Properties allow for the implementation of getters and setters without changing the external API.. Option 1 is wrong; properties don't provide thread safety. Option 2 is correct; they allow adding logic to attribute access while keeping usage as simple as 'obj.x'. Option 3 is wrong. Option 4 is wrong because there are other ways to restrict access, though properties are the standard way.

    From lesson: Python Interview Questions — OOP

  366. What is the primary difference between a shallow copy and a deep copy in the context of the copy module?

    • A shallow copy creates a new object and inserts references to the original's child objects; a deep copy creates a new object and recursively copies all objects found in the original.
    • A shallow copy copies the object's metadata; a deep copy copies the actual byte code of the object.
    • A shallow copy is faster because it does not copy the references; a deep copy is slower because it copies all references.
    • A shallow copy is intended for primitive types only; a deep copy is for user-defined classes.

    Answer: A shallow copy creates a new object and inserts references to the original's child objects; a deep copy creates a new object and recursively copies all objects found in the original.. Option 0 accurately describes the memory management of both techniques. The others are incorrect because they mischaracterize the fundamental nature of references versus recursive cloning.

    From lesson: Python Interview Questions — Advanced

  367. When using a decorator that accepts arguments, why is a three-level nested function structure necessary?

    • It is a syntactic requirement enforced by the Python parser for memory allocation.
    • The outermost function accepts the decorator arguments, the second receives the function to be decorated, and the third executes the wrapper logic.
    • It is required to allow the decorator to be used as a context manager simultaneously.
    • It prevents the function from being garbage collected while the decorator is active.

    Answer: The outermost function accepts the decorator arguments, the second receives the function to be decorated, and the third executes the wrapper logic.. Option 1 correctly identifies the scope requirements of the outer, middle, and inner functions. Other options are irrelevant as they don't explain the scoping necessity for passing arguments to a decorator.

    From lesson: Python Interview Questions — Advanced

  368. What happens when you call a generator's .close() method?

    • The generator is paused and can be resumed later from the same instruction.
    • The generator raises a GeneratorExit exception internally at the point where it is currently suspended.
    • The generator is immediately deleted from memory to free resources.
    • The generator executes all remaining code in the function before closing.

    Answer: The generator raises a GeneratorExit exception internally at the point where it is currently suspended.. Calling .close() triggers a GeneratorExit exception, allowing the generator to run its 'finally' blocks for cleanup. Other options incorrectly describe resource management or execution flow.

    From lesson: Python Interview Questions — Advanced

  369. In the context of Python descriptors, what is the significance of the __set_name__ method?

    • It allows the descriptor to store data in the instance's __dict__ directly.
    • It enables the descriptor to know the name of the attribute it is assigned to in the owner class.
    • It forces the descriptor to become a read-only property.
    • It is used to define the string representation of the descriptor.

    Answer: It enables the descriptor to know the name of the attribute it is assigned to in the owner class.. __set_name__ was introduced to allow descriptors to know their own attribute name on the class, avoiding hardcoding strings. Other options misidentify its purpose as storage, access restriction, or formatting.

    From lesson: Python Interview Questions — Advanced

  370. How does the 'super()' function resolve method calls in multiple inheritance?

    • It always calls the parent class defined first in the class inheritance list.
    • It traverses the Method Resolution Order (MRO) list using the C3 linearization algorithm.
    • It randomly selects an implementation from one of the base classes.
    • It prioritizes the class that was defined most recently in the module.

    Answer: It traverses the Method Resolution Order (MRO) list using the C3 linearization algorithm.. Python uses C3 linearization to create a deterministic MRO, which super() follows. The other options suggest non-existent or incorrect mechanisms for method resolution.

    From lesson: Python Interview Questions — Advanced

  371. When converting a nested loop approach (O(n*k)) for a fixed-size subarray sum into a Sliding Window approach (O(n)), what is the fundamental operation that enables the performance gain?

    • Replacing the inner loop with a set to keep track of elements.
    • Subtracting the outgoing element and adding the incoming element to the previous sum.
    • Sorting the array before processing to ensure values are predictable.
    • Using recursion to calculate the sum of the remaining window.

    Answer: Subtracting the outgoing element and adding the incoming element to the previous sum.. Option 1 is correct because it maintains a running sum, allowing O(1) updates per step. Option 0 is inefficient for summation. Option 2 increases complexity to O(n log n). Option 3 introduces unnecessary overhead and potential stack overflow.

    From lesson: Python Coding Patterns — Sliding Window

  372. In a variable-size sliding window problem where we find the smallest subarray with a sum greater than S, when should the 'left' pointer be incremented?

    • Every time the 'right' pointer increments.
    • Only when the current window sum is exactly equal to S.
    • Repeatedly until the window sum is no longer greater than S.
    • When the 'right' pointer reaches the end of the array.

    Answer: Repeatedly until the window sum is no longer greater than S.. Option 2 is correct because the goal is to shrink the window as much as possible while maintaining the constraint. Option 0 would result in a fixed-size window. Option 1 is incorrect as we need the sum to be greater than S. Option 3 is a terminal condition, not a logic step.

    From lesson: Python Coding Patterns — Sliding Window

  373. If you are using a sliding window to find the longest substring with at most K distinct characters, why is a dictionary (hash map) an effective data structure to include?

    • It maintains the order of elements for easy slicing.
    • It allows for O(1) lookup of indices for fast jumping.
    • It tracks the frequency of characters currently in the window to monitor the 'distinct' count.
    • It automatically sorts the characters alphabetically.

    Answer: It tracks the frequency of characters currently in the window to monitor the 'distinct' count.. Option 2 is correct because the dictionary tracks the count of each character, letting us know when we hit K distinct characters. Option 0 is better handled by lists or queues. Option 1 doesn't help with counting frequencies. Option 3 is false as dictionaries are unordered in older Python and insertion order in newer, but they do not sort by character value.

    From lesson: Python Coding Patterns — Sliding Window

  374. What is the primary constraint that differentiates a 'Fixed' sliding window from a 'Dynamic' sliding window?

    • Fixed windows use two pointers; dynamic windows use one.
    • Fixed windows have a constant length k; dynamic windows change length based on a condition.
    • Fixed windows require a hash map; dynamic windows require a list.
    • Fixed windows are used for searching; dynamic windows are used for sorting.

    Answer: Fixed windows have a constant length k; dynamic windows change length based on a condition.. Option 1 is the definition of the patterns. Both types use two pointers. Hash maps and lists can be used in either. Both patterns are used for searching and processing, not sorting.

    From lesson: Python Coding Patterns — Sliding Window

  375. In a sliding window algorithm, what represents the most efficient way to maintain the window state as you move from index 'i' to 'i+1'?

    • Re-processing all elements within the new window bounds.
    • Comparing the new element to the entire original array.
    • Updating the state using only the element entering and the element leaving the window.
    • Clearing the current window state and rebuilding it entirely.

    Answer: Updating the state using only the element entering and the element leaving the window.. Option 2 is the core optimization of the sliding window, keeping complexity linear. Option 0 turns the algorithm into O(n*k). Option 1 is inefficient. Option 3 resets the progress, defeating the purpose of sliding.

    From lesson: Python Coding Patterns — Sliding Window

  376. In a sorted array, if the sum of elements at the left and right pointers is less than the target value, what should you do?

    • Decrement the right pointer
    • Increment the left pointer
    • Increment both pointers
    • Move both pointers to the center

    Answer: Increment the left pointer. Incrementing the left pointer increases the total sum because the array is sorted. Decrementing the right pointer would decrease the sum, making it further from the target. Moving both or decrementing the right is illogical in a sorted search.

    From lesson: Python Coding Patterns — Two Pointers

  377. Why is the Two Pointer pattern generally more efficient than a nested loop approach for finding a pair sum?

    • It uses less memory than nested loops
    • It allows for O(n log n) sorting, while nested loops are always slower
    • It reduces the time complexity from O(n^2) to O(n)
    • It is always O(1) space complexity regardless of input

    Answer: It reduces the time complexity from O(n^2) to O(n). Two pointers allow us to scan the array in a single pass after sorting, resulting in linear time complexity. Nested loops are quadratic O(n^2). While it does use O(1) space, that is not the primary reason for its efficiency relative to nested loops.

    From lesson: Python Coding Patterns — Two Pointers

  378. When using two pointers to remove duplicates from a sorted array in-place, what is the role of the 'slow' pointer?

    • It marks the position of the last unique element found
    • It iterates through the entire list to find new values
    • It skips over elements that are equal to the target
    • It keeps track of the total count of duplicates

    Answer: It marks the position of the last unique element found. The slow pointer maintains the boundary of the 'processed' unique list. The fast pointer searches for new values. Option B describes the fast pointer, while C and D are not standard functions of the slow pointer in this specific pattern.

    From lesson: Python Coding Patterns — Two Pointers

  379. What is the primary condition required to use the bidirectional two-pointer approach for finding pairs?

    • The list must contain only positive integers
    • The list must be sorted in ascending order
    • The list must be converted to a dictionary first
    • The list must have an even number of elements

    Answer: The list must be sorted in ascending order. The bidirectional approach relies on the property that moving a pointer inward predictably changes the sum (increasing from left, decreasing from right). This only holds if the list is sorted. Positive integers, dictionaries, and even lengths are not requirements.

    From lesson: Python Coding Patterns — Two Pointers

  380. If you are asked to 'square a sorted array' and return the result in sorted order, why is two-pointer the best approach?

    • It avoids the O(n log n) cost of sorting the squared values
    • Squaring makes the array unsorted, so pointers are needed to re-sort
    • It is faster than just squaring and calling .sort()
    • It prevents index out of bounds errors

    Answer: It avoids the O(n log n) cost of sorting the squared values. Squaring a sorted list (e.g., [-4, -1, 0, 3, 10]) results in values where the largest numbers are at the ends. Two pointers allow you to pick the largest squares from the ends and place them into a new array backwards in O(n) time, avoiding a full re-sort.

    From lesson: Python Coding Patterns — Two Pointers

  381. What is the primary purpose of the 'unchoose' step in a backtracking algorithm?

    • To terminate the recursion early.
    • To reset the state to allow other branches to explore from a clean slate.
    • To optimize the memory usage of the recursion stack.
    • To permanently save the successful path found so far.

    Answer: To reset the state to allow other branches to explore from a clean slate.. The 'unchoose' step ensures that modifications made to the state during exploration are reversed, which is required for correct backtracking to other branches. Option 0 is the base case's job, 2 is unrelated to state cleanup, and 3 is incorrect because we typically keep track of the result separately.

    From lesson: Python Coding Patterns — Recursion and Backtracking

  382. Consider a recursive function calculating factorials. What happens if the recursive step 'return n * factorial(n)' is used instead of 'return n * factorial(n - 1)'?

    • It will return 0.
    • It will return an infinite loop of the same number.
    • It will result in a RecursionError due to exceeding the maximum depth.
    • It will correctly return the factorial value.

    Answer: It will result in a RecursionError due to exceeding the maximum depth.. Because n never decreases to reach the base case (0 or 1), the function calls itself with the same value infinitely until the stack overflows. Options 0, 1, and 3 are incorrect because they ignore the stack overflow behavior.

    From lesson: Python Coding Patterns — Recursion and Backtracking

  383. Which of these is the most effective way to handle overlapping sub-problems in a recursive function?

    • Increasing the recursion depth limit.
    • Using a global variable to store the result.
    • Applying memoization to cache results of expensive calls.
    • Converting the function into a class method.

    Answer: Applying memoization to cache results of expensive calls.. Memoization caches results of sub-problems, preventing redundant computations and drastically reducing complexity. Option 0 doesn't fix performance, 1 is bad practice/unsafe, and 3 does not address the efficiency of redundant calls.

    From lesson: Python Coding Patterns — Recursion and Backtracking

  384. In Python, what is the default behavior when a recursive function reaches the maximum allowed depth?

    • The program crashes with a MemoryError.
    • The program exits silently.
    • The program raises a RecursionError.
    • The program automatically switches to an iterative approach.

    Answer: The program raises a RecursionError.. Python has a built-in recursion limit to prevent stack overflows and explicitly raises a RecursionError when this limit is hit. Options 0, 1, and 3 describe behaviors that do not happen in standard Python.

    From lesson: Python Coding Patterns — Recursion and Backtracking

  385. When implementing a backtracking algorithm to find all permutations of a list, why is copying the current path before appending it to the result list necessary?

    • To ensure the recursion depth is correctly maintained.
    • To prevent subsequent modifications in the backtracking process from changing the stored results.
    • To allow the function to run in parallel.
    • To satisfy the memory requirement of the interpreter.

    Answer: To prevent subsequent modifications in the backtracking process from changing the stored results.. Because lists are mutable, if you append the reference of the current path, all stored results will reflect the final state of that list. Creating a copy preserves the state at that moment. The other options are incorrect as they do not address the mutability issue.

    From lesson: Python Coding Patterns — Recursion and Backtracking

  386. What is the time complexity of checking if an element exists in a Python set containing n elements?

    • O(1) on average
    • O(log n)
    • O(n)
    • O(n log n)

    Answer: O(1) on average. Sets are implemented as hash tables, allowing O(1) average lookup. O(log n) is typical for trees, O(n) is for lists, and O(n log n) is for efficient sorting algorithms.

    From lesson: Big-O Complexity in Python

  387. Given a list 'data' of length n, what is the complexity of executing 'data.pop(n-1)'?

    • O(n)
    • O(1)
    • O(log n)
    • O(n^2)

    Answer: O(1). Removing the last element of a list does not require re-indexing, making it an O(1) operation. O(n) is for removing from the front, and the others do not apply to simple list removal.

    From lesson: Big-O Complexity in Python

  388. If you iterate through a nested list (a list of lists) where the outer list has length N and each inner list has length M, what is the time complexity?

    • O(N + M)
    • O(N * M)
    • O(N^2)
    • O(M^2)

    Answer: O(N * M). A nested loop covering every element in a structure of N lists with M elements each results in N*M iterations. The other options either undercount or misapply complexity rules.

    From lesson: Big-O Complexity in Python

  389. Why is using ''.join(list_of_strings) preferred over repeatedly adding strings using '+' in a loop?

    • It uses less RAM globally.
    • It is O(n) compared to the O(n^2) of repeated concatenation.
    • It is O(log n) compared to O(n).
    • It automatically sorts the characters.

    Answer: It is O(n) compared to the O(n^2) of repeated concatenation.. Repeated string concatenation is O(n^2) because each step creates a new string copy. join() pre-calculates the size and performs one copy, resulting in O(n). It does not sort or save global RAM by definition.

    From lesson: Big-O Complexity in Python

  390. Which of the following operations on a Python dictionary is expected to take O(n) time, where n is the number of keys?

    • dict[key] = value
    • del dict[key]
    • list(dict.values())
    • key in dict

    Answer: list(dict.values()). Extracting all values into a list requires visiting every entry in the dictionary, which is O(n). The other three operations are O(1) average time because they rely on hash lookups.

    From lesson: Big-O Complexity in Python