AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Grade 8 AI & Computer Science Practice Questions — Set 5

20 questions from the Grade 8 bank, each with its answer and a full explanation. Set 5 of 11 · 221 questions in this grade.

Reading is revision; testing is practice. Take the same questions as a timed quiz →

Question 81 · list comprehensions · hard

Analyze: 'result = [x for x in range(20) if x % 3 == 0 and x % 5 == 0]'. What values are included in result, and explain why using 'and' in comprehension conditions enables multi-criteria filtering? For the given code, consider how this pattern affects execution and analyze what the result demonstrates about Python semantics?

  1. The result is [0, 15] because the condition filters elements divisible by BOTH 3 AND 5 (i.e., divisible by 15), requiring x % 3 == 0 AND x % 5 == 0 to both evaluate true before inclusion in the output
  2. The result is [3, 5, 6, 9, 10, 12, 15, 18] because the condition includes numbers divisible by 3 OR 5, using implicit or logic in comprehension filtering
  3. The result is [0, 3, 5, 6, 9, 10, 12, 15, 18] because 'and' is ignored in comprehension conditions, treating the filter as if only x % 3 == 0 exists
  4. The result is empty because range(20) evaluates the 'and' condition before generating any values, so the comprehension exits without checking x % 3 == 0 or x % 5 == 0 for any element

Answer: A. The result is [0, 15] because the condition filters elements divisible by BOTH 3 AND 5 (i.e., divisible by 15), requiring x % 3 == 0 AND x % 5 == 0 to both evaluate true before inclusion in the output

ExplanationFirst, the result is [0, 15], because Evaluation: range(20) produces 0,1,2,...,19. The condition x % 3 == 0 and x % 5 == 0 requires both parts true (logical AND). Then, values divisible by 3: 0,3,6,9,12,15,18. Values divisible by 5: 0,5,10,15. Intersection (both conditions true): 0, 15. This demonstrates that 'and' in comprehension conditions enables multi-criteria filtering—the same value must pass all conditions. Mathematically, x % 3 == 0 and x % 5 == 0 is equivalent to x % 15 == 0 (least common multiple). Using 'and' filters for intersection (stricter), while 'or' filters for union (looser). Without multiple conditions, [x for x in range(20) if x % 3 == 0] produces [0,3,6,9,12,15,18]. With 'and': [0,15]. With 'or': [0,3,5,6,9,10,12,15,18]. Finally, comprehensions make such multi-criteria selections readable.

Question 82 · dictionary operations · hard

Analyze this code: 'student = {'name': 'Alice', 'age': 15, 'grade': 'A'}; value = student.get('age')'. What value is returned by get(), and explain why get() is safer than direct bracket notation for accessing dictionary keys?

  1. The value 15 is returned because get('age') retrieves the value associated with the 'age' key, and unlike student['age'], get() returns None instead of raising KeyError if the key does not exist, making it safer for defensive programming
  2. The value 'age' is returned because get() extracts the key itself rather than its associated value, demonstrating a different retrieval mechanism from bracket notation
  3. The value 'Alice' is returned because dictionaries are ordered by insertion, and get() returns the first value regardless of which key is requested
  4. A KeyError exception is raised because dictionaries do not support the get() method, and only bracket notation is valid for dictionary access

Answer: A. The value 15 is returned because get('age') retrieves the value associated with the 'age' key, and unlike student['age'], get() returns None instead of raising KeyError if the key does not exist, making it safer for defensive programming

ExplanationFirst, the value 15 is returned by student.get('age'), because the get() method retrieves the value associated with 'age'. Difference from bracket notation: student['age'] returns 15 if the key exists, but raises a KeyError if it is missing. get('age') instead returns 15 if the key exists, or None if it is missing — it never raises an exception. Why this matters: (1) Error handling — code will not crash on a missing key, (2) Default values — get('missing', 'default') can return a fallback like 'default' instead of None, (3) Safety when processing data that may have optional fields. In professional code, get() is the preferred choice whenever a key's presence cannot be guaranteed.

Question 83 · dictionary operations · hard

Consider: 'data = {'x': 10, 'y': 20, 'z': 30}; keys = data.keys(); result = 'x' in keys'. What is result, and analyze how the keys() method provides access to dictionary keys as an iterable collection?

  1. The result is True because data.keys() returns a dict_keys object containing 'x', 'y', 'z', and the 'in' operator checks membership, finding 'x' in this collection, enabling O(1) membership testing on dictionary keys
  2. The result is False because keys() returns only the values, not the keys, so checking for 'x' (a key) fails when searching through values
  3. The result is undefined because dict_keys is not a sequence type, and the 'in' operator cannot be used with membership testing on this object type
  4. The result is a list ['x', 'y', 'z'] because keys() returns the keys directly rather than returning a boolean from the 'in' check

Answer: A. The result is True because data.keys() returns a dict_keys object containing 'x', 'y', 'z', and the 'in' operator checks membership, finding 'x' in this collection, enabling O(1) membership testing on dictionary keys

ExplanationFirst, the result is True. data.keys() returns a dict_keys object containing 'x', 'y', 'z'. Then, the expression 'x' in keys checks membership. Since 'x' is indeed a key, the result is True. The keys() method returns a dynamic view of dictionary keys. Membership testing is O(1) average-case on dictionary keys due to hash tables, while on lists it is O(n) linear search. Finally, professional code frequently uses: if user_id in user_database.keys().

Question 84 · dictionary operations · hard

Given the code `d = {'a': 1, 'b': 2, 'c': 3}; d.pop('b'); result = d`, what is the value of result, and how does pop() modify the dictionary while returning the removed value?

  1. The result is {'a': 1, 'c': 3} because pop('b') removes the key 'b' and its value 2 from the dictionary and returns 2, modifying the original dictionary in-place, making pop() useful for extracting and removing elements in a single operation
  2. Result equals {'a': 1, 'b': 2, 'c': 3} because pop() returns the value but does not modify the dictionary, leaving the key-value pair intact
  3. The result is 2 because pop() returns the removed value, not the remaining dictionary, so result = 2, not the modified dictionary
  4. The result is a KeyError because pop() requires two arguments (key and default value), and calling it with only the key raises an exception

Answer: A. The result is {'a': 1, 'c': 3} because pop('b') removes the key 'b' and its value 2 from the dictionary and returns 2, modifying the original dictionary in-place, making pop() useful for extracting and removing elements in a single operation

Explanationpop('b') removes the key 'b' from the dictionary and returns its value, 2, while modifying the dictionary in place. After the call, d contains {'a': 1, 'c': 3}. Since result is assigned from d, not from the return value of pop(), result equals {'a': 1, 'c': 3}, showing that pop() both removes a key-value pair and hands back the removed value in a single operation.

Question 85 · dictionary operations · hard

Given: 'd = {'one': 1, 'two': 2, 'three': 3}; for key, value in d.items(): print(key, value)'. What is printed, and explain how items() enables simultaneous access to both keys and values in dictionary iteration, and evaluate how this affects system behavior?

  1. The output prints 'one 1', 'two 2', 'three 3' (each on separate lines) because items() returns tuples (key, value) that can be unpacked into separate variables in the loop, enabling simultaneous access to both components in O(n) time for n keys
  2. The output prints only keys: 'one', 'two', 'three' because items() extracts only the keys for iteration, excluding values from the output
  3. The output prints TypeError because the loop syntax for key, value is invalid for dictionary iteration without explicit tuple unpacking
  4. The output prints values only: '1 2 3' because items() returns values in a single tuple rather than individual (key, value) pairs

Answer: A. The output prints 'one 1', 'two 2', 'three 3' (each on separate lines) because items() returns tuples (key, value) that can be unpacked into separate variables in the loop, enabling simultaneous access to both components in O(n) time for n keys

ExplanationThe dictionary d has three key-value pairs, so d.items() yields three tuples: ('one', 1), ('two', 2), and ('three', 3). The loop header for key, value in d.items(): unpacks each tuple directly into the variables key and value, so print(key, value) outputs 'one 1', 'two 2', and 'three 3' on three separate lines, in insertion order (Python 3.7+ dictionaries preserve insertion order). This unpacking is what makes items() valuable: iterating with for key in d alone exposes only the keys, forcing a separate d[key] lookup to fetch each value, whereas items() retrieves both the key and its value together in a single O(n) pass over the n entries, with no repeated lookups. That is why items() is the standard, PEP 8-recommended way to iterate a dictionary whenever both keys and values are needed.

Question 86 · dictionary operations · hard

Evaluate: 'd = {'x': 10, 'y': 20}; values_list = list(d.values()); total = sum(values_list)'. What is total, and analyze how values() enables aggregation operations on all dictionary values? For the given code, consider how this pattern affects execution and analyze what the result demonstrates about Python semantics?

  1. The total is 30 because values() returns a dict_values view containing 10, 20, and sum() computes 10 + 20 = 30, demonstrating how values() provides access to all values for aggregation without knowing keys
  2. The total is {'x': 10, 'y': 20} because sum() returns the original dictionary when applied to value views, not the numeric sum
  3. The total is a TypeError because sum() requires a list, not a dict_values view, so list() conversion is necessary
  4. The total is 10 because values() returns only the first value, ignoring 'y': 20 in the aggregation

Answer: A. The total is 30 because values() returns a dict_values view containing 10, 20, and sum() computes 10 + 20 = 30, demonstrating how values() provides access to all values for aggregation without knowing keys

ExplanationThe values() method returns a dict_values view containing 10 and 20. Converting it with list() gives [10, 20], and sum() adds these together: 10 + 20 = 30. This is why values() is useful for aggregation—it hands over all the values in a dictionary directly, without needing to know or loop through the keys, so operations like sum(), max(), or statistics.mean() can be applied straight to d.values().

Question 87 · OOP classes · hard

Given: 'class Counter: def __init__(self, start=0): self.count = start; def increment(self): self.count += 1; c = Counter(5); c.increment(); result = c.count'. What is result, and explain how __init__() initializes instance state in classes, and evaluate how this affects system behavior?

  1. The result is 6 because __init__(self, start=0) initializes self.count = 5, then increment() adds 1 to get 6, demonstrating how __init__() sets initial instance attributes that methods can access and modify
  2. The result is 5 because increment() operates on class state, not instance state, so modifications do not affect the instance's count attribute
  3. The result is 1 because increment() resets the counter regardless of initial value, overriding __init__() behavior
  4. The result is undefined because __init__() requires explicit calls, which were not made in the code

Answer: A. The result is 6 because __init__(self, start=0) initializes self.count = 5, then increment() adds 1 to get 6, demonstrating how __init__() sets initial instance attributes that methods can access and modify

ExplanationFirst, the result is 6. The __init__() method initializes self.count = 5. Then increment() executes self.count += 1, changing 5 to 6. Result = c.count = 6. This demonstrates that __init__() runs automatically when Counter(5) is called, initializing instance attributes. Each instance has separate state—c.count and another Counter object's count are independent. Finally, __init__() is critical because it sets up initial state that methods rely on.

Question 88 · OOP inheritance · hard

Consider: 'class Vehicle: pass; class Car(Vehicle): pass; c = Car(); result = isinstance(c, Vehicle)'. What is result, and analyze how inheritance enables type compatibility checking? For the given code, consider how this pattern affects execution and analyze what the result demonstrates about Python semantics?

  1. The result is True because Car inherits from Vehicle, making instances of Car also instances of Vehicle types, enabling isinstance() to recognize subclass instances as parent class instances
  2. The result is False because isinstance() only checks direct class membership, not inheritance relationships
  3. The result raises TypeError because isinstance() does not work with custom classes, only built-in types
  4. The result is undefined because Car does not explicitly call Vehicle's methods, so inheritance is incomplete

Answer: A. The result is True because Car inherits from Vehicle, making instances of Car also instances of Vehicle types, enabling isinstance() to recognize subclass instances as parent class instances

ExplanationThe result is True. Car inherits from Vehicle, so instances of Car are also instances of Vehicle from a type perspective. isinstance(c, Vehicle) returns True because Car IS-A Vehicle — Python's isinstance() checks the entire class hierarchy, not just an object's exact class. This is what makes polymorphic code possible: a function like def process(v): if isinstance(v, Vehicle): v.start() works correctly for Car, Truck, or Motorcycle objects since all of them inherit from Vehicle, without the function ever needing to know the specific subclass it received.

Question 89 · OOP inheritance · hard

Analyze: 'class Base: x = 10; class Derived(Base): x = 20; result = Derived().x'. What is result, and explain how attribute lookup follows method resolution order in inheritance hierarchies?

  1. The result is 20 because Derived overrides Base.x with 20, and attribute lookup finds Derived.x first in the MRO (method resolution order), returning the derived class's attribute before checking the parent
  2. The result is 10 because Base is the parent class, so its attributes take precedence over derived class attributes
  3. The result is undefined because class attributes cannot be overridden in inheritance, so Python would raise an error the moment Derived tries to redefine an attribute already set on Base
  4. The result is 'Base.x Derived.x' because Python concatenates the attribute value from every class in the inheritance chain when it performs attribute lookup

Answer: A. The result is 20 because Derived overrides Base.x with 20, and attribute lookup finds Derived.x first in the MRO (method resolution order), returning the derived class's attribute before checking the parent

ExplanationThe result is 20, because Python's method resolution order (MRO) checks Derived first, finding x = 20, and returns it without checking Base. If Derived did not define x, it would inherit Base.x = 10. MRO order: [Derived, Base, object]. This enables intentional overriding: subclasses can shadow parent attributes. Professional code uses this for configuration: BaseConfig.debug = False; DevelopmentConfig.debug = True enables environment-specific settings through inheritance.

Question 90 · OOP dunder methods · hard

Given: 'class BankAccount: def __init__(self, balance): self._balance = balance; def __str__(self): return f'Balance: {self._balance}'; acc = BankAccount(100); result = str(acc)'. What is result, and evaluate how __str__() enables custom string representation?

  1. The result is 'Balance: 100' because __str__() method defines custom string representation returned by str() function, enabling readable output instead of default memory address, demonstrating dunder method customization
  2. The result is '<BankAccount object at 0x...>' because __str__() is not called unless explicitly invoked, and str() falls back to the default object representation
  3. The result is undefined because str() does not work with custom classes without special setup
  4. The result is 'BankAccount' because __str__() only returns the class name, not instance attributes

Answer: A. The result is 'Balance: 100' because __str__() method defines custom string representation returned by str() function, enabling readable output instead of default memory address, demonstrating dunder method customization

ExplanationFirst, the result is 'Balance: 100', because When str(acc) is called, Python invokes the __str__() method, which returns the formatted string 'Balance: 100'. Without __str__(), str(acc) would return '<BankAccount object at 0x7f...>' (memory address). Then, dunder methods (double-underscore methods) customize behavior for built-in operations. Professional code implements __str__() for all classes to aid debugging and logging.

Question 91 · OOP design patterns · hard

Analyze: 'class Counter: _instance = None; def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls); return cls._instance; c1 = Counter(); c2 = Counter(); result = c1 is c2'. What is result, and explain how __new__() controls instance creation in singleton pattern?

  1. The result is True because __new__() creates only one instance (stored in _instance), returning the same object to both c1 and c2, demonstrating the singleton pattern where a class has at most one instance
  2. The result is False because each call to Counter() invokes __new__() independently, allocating a fresh object every time regardless of what the _instance check finds
  3. The result raises TypeError because __new__() must always accept extra positional arguments matching __init__()'s signature, and this class defines __new__() with only cls
  4. The result is undefined because _instance is a class variable that does not persist between calls

Answer: A. The result is True because __new__() creates only one instance (stored in _instance), returning the same object to both c1 and c2, demonstrating the singleton pattern where a class has at most one instance

ExplanationFirst, the result is True, because __new__() controls instance creation (it runs before __init__()). The code checks if _instance exists; if not, it creates the instance and caches it; otherwise it returns the already-cached instance. So c1 and c2 end up referencing the identical object, and c1 is c2 evaluates to True. This is the singleton pattern — it guarantees a class has at most one instance. Common real-world uses include a single Logger for an entire app, a single Configuration object, or a single shared database connection pool.

Question 92 · file I/O · hard

Given the following Python code: ```python import json data = {'name': 'Alice', 'age': 15} with open('data.json', 'w') as f: json.dump(data, f) result = 'saved' ``` What happens when this code runs, and how does JSON serialization enable data persistence?

  1. The dictionary is serialized to JSON format as {"name": "Alice", "age": 15} and written to the file, enabling structured data persistence in a human-readable format that other languages can parse, demonstrating data interchange beyond Python
  2. json.dump() raises TypeError because dictionaries cannot be serialized to JSON
  3. The file is created but remains empty because dump() does not actually write content to the file
  4. The result is the file path because dump() returns the location where data was saved

Answer: A. The dictionary is serialized to JSON format as {"name": "Alice", "age": 15} and written to the file, enabling structured data persistence in a human-readable format that other languages can parse, demonstrating data interchange beyond Python

Explanationjson.dump(data, f) serializes the dictionary to JSON format and writes it to the file. The file ends up containing {"name": "Alice", "age": 15} — Python's single quotes become the double quotes JSON requires, since the JSON specification mandates double quotes around keys and string values. JSON serialization enables data persistence in three ways: it saves program state to disk, it lets other languages parse the same file (interoperability), and it stays human-readable so files can be inspected directly in a text editor.

Question 93 · error handling · hard

Consider: 'class CustomError(Exception): pass; try: raise CustomError('something wrong'); except CustomError as e: result = str(e)'. What is result, and evaluate why custom exceptions enable domain-specific error handling?

  1. The result is 'something wrong' because CustomError is a user-defined exception inheriting from Exception, and 'raise CustomError(msg)' creates an exception instance with the message, caught as 'e' and converted to string, enabling custom error types for application domains
  2. str(e) returns 'CustomError('something wrong')' instead of just the message, because Python's default exception string conversion includes the class name and full constructor arguments rather than isolating the message text alone
  3. Raising CustomError actually triggers a TypeError at the except clause, because subclassing Exception without defining an __init__ method prevents the parent class from storing the message that raise CustomError('something wrong') attempts to pass
  4. The result is just 'CustomError' with no message attached, because Python's default exception handling silently discards the message argument for any exception class that inherits directly from Exception rather than from a more specific built-in exception type

Answer: A. The result is 'something wrong' because CustomError is a user-defined exception inheriting from Exception, and 'raise CustomError(msg)' creates an exception instance with the message, caught as 'e' and converted to string, enabling custom error types for application domains

ExplanationThe result is 'something wrong', because CustomError(msg) creates an exception instance with msg stored as its argument. Raising it with raise generates that exception object, caught as variable e, and str(e) converts it to its message text since Exception's built-in __str__ already returns the constructor arguments as a string, with no override needed. Custom exceptions like this enable domain-specific error handling: a program can write except CustomError: handle_app_error() separately from except ValueError: handle_type_error(), so different failure types trigger different, targeted recovery logic instead of one generic handler treating every error the same way.

Question 94 · error handling · hard

Consider: 'import traceback; try: cause_error(); except Exception: error_info = traceback.format_exc(); result = len(error_info) > 0'. What is result, and analyze how traceback enables debugging of exception sources?

  1. The result is True because traceback.format_exc() returns the full exception stack trace as a string (file, line, exception type, message), enabling detailed debugging information about where the error occurred and what caused it
  2. The result is False, since traceback.format_exc() returns None whenever it is called without first printing the exception, causing len() to raise a TypeError instead of producing a length
  3. The result is False because format_exc() only works when the exception object is passed to it explicitly as an argument; calling it with no arguments always returns an empty string
  4. The result is True, but traceback.format_exc() records only the exception type and message — it does not include the file name, line number, or call stack where the error occurred

Answer: A. The result is True because traceback.format_exc() returns the full exception stack trace as a string (file, line, exception type, message), enabling detailed debugging information about where the error occurred and what caused it

ExplanationThe result is True. Inside the except block, traceback.format_exc() captures the current exception's full traceback as a formatted string — the file path, line number, exception type, and message, plus the complete chain of function calls that led to the error. Because cause_error() actually raised an exception that was caught, this string is guaranteed to be non-empty, so len(error_info) > 0 evaluates to True. This is precisely what makes traceback so valuable for debugging: unlike just catching the exception and printing its message, format_exc() preserves the entire call stack, so a developer can see not just what went wrong but exactly which function called which function down to the line that failed. In production systems this trace is usually written to a log file with logging.exception() rather than printed to the console, so it can be reviewed after the program has already moved on.

Question 95 · string operations · hard

Consider: 'text = 'hello'; char = text[2]; result = char'. What is result, and explain how string indexing enables character access?

  1. The result is 'l' because strings are indexable like lists, and text[2] accesses the 3rd character (0-indexed: h=0, e=1, l=2), enabling character-level operations and substring extraction in O(1) time
  2. Strings do not support indexing; only lists support bracket notation.
  3. The result is 'e' because Python string indexing counts from 1, not 0.
  4. The result raises TypeError because accessing a character requires a method call, not bracket indexing.

Answer: A. The result is 'l' because strings are indexable like lists, and text[2] accesses the 3rd character (0-indexed: h=0, e=1, l=2), enabling character-level operations and substring extraction in O(1) time

ExplanationThe result is 'l' because strings are sequences and indexing is 0-based: h=0, e=1, l=2, l=3, o=4, so text[2] returns 'l'. Slicing text[1:3] would give 'el' (the characters at indices 1 and 2), which confirms the same 0-based counting.

Question 96 · recursion · hard

Evaluate: 'def factorial(n): if n <= 1: return 1; else: return n * factorial(n-1); result = factorial(5)'. What is result, and predict how recursion unwinds through multiple calls to compute the final answer?

  1. The result is 120 because factorial(5) = 5 * factorial(4) = 5 * (4 * factorial(3)) = 5 * 4 * 3 * 2 * 1 = 120, demonstrating how recursion decomposes into base cases then recomposes results, with call stack growing to depth n
  2. The result is 5 because recursion only goes one level deep, treating factorial(5) as simply returning n itself without performing any further multiplication
  3. The result raises RecursionError because Python does not allow a function to call itself from within its own body
  4. The result is undefined because the base case n <= 1 is never reached as n decreases from 5 down through each recursive call

Answer: A. The result is 120 because factorial(5) = 5 * factorial(4) = 5 * (4 * factorial(3)) = 5 * 4 * 3 * 2 * 1 = 120, demonstrating how recursion decomposes into base cases then recomposes results, with call stack growing to depth n

ExplanationThe result is 120. Trace: factorial(5) = 5 * factorial(4); factorial(4) = 4 * factorial(3); factorial(3) = 3 * factorial(2); factorial(2) = 2 * factorial(1); factorial(1) = 1 (base case, since n <= 1). The calls then unwind from the base case outward: 1, then 1*2=2, then 2*3=6, then 6*4=24, then 24*5=120. This demonstrates recursion: the function calls itself with a simpler input (n-1) until the base case stops further recursion, and the pending multiplications resolve as each call returns, with the call stack reaching a depth of 5 before unwinding back to the final result.

Question 97 · recursion · hard

Consider: 'def fib(n): if n <= 1: return n; else: return fib(n-1) + fib(n-2); result = fib(5)'. What is result, and analyze why this naive recursion is inefficient compared to memoization? For the given code, consider how this pattern affects execution and analyze what the result demonstrates about Python semantics?

  1. The result is 5 because fib(5) recomputes overlapping subproblems (fib(3) computed 2 times, fib(2) computed 3 times, etc.), demonstrating exponential O(2^n) complexity that memoization reduces to O(n) by caching results
  2. The result is 13 because the recursive calls skip fib(2), causing the sequence to jump ahead by two positions to fib(7) instead of stopping at fib(5)
  3. The result is 4 because the base case 'if n <= 1: return n' actually returns n-1 in Python, shifting every value in the sequence down by one
  4. The result computes instantly because Python automatically memoizes every function call by default, so fib(3) and fib(2) are only ever evaluated once regardless of how many times they are called

Answer: A. The result is 5 because fib(5) recomputes overlapping subproblems (fib(3) computed 2 times, fib(2) computed 3 times, etc.), demonstrating exponential O(2^n) complexity that memoization reduces to O(n) by caching results

ExplanationThe result is 5, following the Fibonacci sequence 0, 1, 1, 2, 3, 5. The recursion is inefficient because it recomputes overlapping subproblems: fib(5) calls fib(4) and fib(3), and fib(4) itself calls fib(3) and fib(2), so fib(3) ends up computed 2 times and fib(2) ends up computed 3 times across the full call tree. This redundant branching gives the naive version exponential O(2^n) time complexity. Memoization avoids the repetition by caching each fib(k) the first time it is computed, so every subproblem is solved only once and the total time drops to O(n).

Question 98 · sorting · hard

Given: 'arr = [3, 1, 4, 1, 5, 9, 2, 6]; sorted_arr = sorted(arr); result = sorted_arr'. What is result, and evaluate why sorted() returns a new list instead of modifying the original? For the given code, consider how this pattern affects execution and analyze what the result demonstrates about Python semantics?

  1. The result is [1, 1, 2, 3, 4, 5, 6, 9] because sorted() returns a new list in ascending order without modifying the original array, enabling functional programming where operations do not cause side effects, unlike arr.sort() which mutates in-place
  2. The result is the original [3, 1, 4, ...] because sorted() does not modify the array, which would completely change the algorithm's behavior and produce a fundamentally different result from what is expected
  3. The result is [9, 6, 5, 4, 3, 2, 1, 1] because sorted() defaults to descending order for numeric lists, sorting values from highest to lowest instead of the ascending order Python actually uses by default
  4. The result modifies arr to be sorted, because sorted() works identically to arr.sort() by sorting the list in place and returning None instead of building a separate new list

Answer: A. The result is [1, 1, 2, 3, 4, 5, 6, 9] because sorted() returns a new list in ascending order without modifying the original array, enabling functional programming where operations do not cause side effects, unlike arr.sort() which mutates in-place

Explanationsorted(arr) returns a new list, [1, 1, 2, 3, 4, 5, 6, 9], sorted in ascending order, while the original arr stays as [3, 1, 4, 1, 5, 9, 2, 6]. This is because sorted() is a built-in function that builds and returns a fresh list rather than modifying its argument, unlike the arr.sort() method, which sorts the list in place and returns None. Assigning sorted_arr = sorted(arr) therefore captures the new sorted list, and result (set equal to sorted_arr) holds that same value: [1, 1, 2, 3, 4, 5, 6, 9]. This demonstrates Python's convention of returning new objects from functions that avoid side effects, keeping arr unchanged and available for further use elsewhere in the program.

Question 99 · sorting · hard

Consider this optimized bubble sort with an early-exit flag: ```python def bubble_sort(arr): n = len(arr) for i in range(n): swapped = False for j in range(n - 1 - i): if arr[j] > arr[j + 1]: arr[j], arr[j + 1] = arr[j + 1], arr[j] swapped = True if not swapped: break return arr result = bubble_sort([1, 2, 3, 5, 4]) ``` Tracing the outer loop by hand, how many passes execute before the function returns, and what does this reveal about bubble sort's time complexity?

  1. The outer loop runs exactly two passes: pass one swaps the 5 and 4 into place and sets swapped to True, pass two finds every adjacent pair already ordered and sets swapped to False, triggering the break — this shows bubble sort's best-case time complexity is O(n) when the swapped flag lets it exit early on a nearly sorted array, even though its worst case remains O(n^2)
  2. Bubble sort always executes all n passes regardless of the swapped flag, so this array still forces 5 full passes and O(n^2) comparisons, since the flag only affects which elements get compared, not how many times the outer loop repeats
  3. The loop runs exactly three passes because the swapped flag only turns False once the third comparison in a row finds no out-of-order pair, meaning bubble sort needs at least three clean passes before it can safely terminate early
  4. This trace shows selection sort behavior rather than bubble sort, since after the first pass the largest remaining element is selected and fixed in place while the other four values stay in their original unsorted positions

Answer: A. The outer loop runs exactly two passes: pass one swaps the 5 and 4 into place and sets swapped to True, pass two finds every adjacent pair already ordered and sets swapped to False, triggering the break — this shows bubble sort's best-case time complexity is O(n) when the swapped flag lets it exit early on a nearly sorted array, even though its worst case remains O(n^2)

ExplanationTracing the array [1, 2, 3, 5, 4]: in pass one (i = 0), comparisons at j = 0, 1, 2 find no out-of-order pairs, but at j = 3 the algorithm compares 5 and 4, swaps them to get [1, 2, 3, 4, 5], and sets swapped = True, so the outer loop does not break. In pass two (i = 1), all three comparisons (1 vs 2, 2 vs 3, 3 vs 4) find the array already ordered, so swapped stays False and the `if not swapped: break` statement fires immediately, skipping the remaining three passes the unoptimized version would have run. This means the outer loop executes exactly two passes instead of five. The swapped flag is what makes this possible: without it, bubble sort always runs its full nested loop structure, giving O(n^2) comparisons regardless of how sorted the input already is. With the flag, an array that is already sorted or just one swap away from sorted — like this one — lets the algorithm detect that no more swaps are needed and stop, which is exactly the condition that produces bubble sort's best-case O(n) time complexity. The worst case, such as a fully reverse-sorted array, still forces all n passes and remains O(n^2).

Question 100 · data structures · hard

Analyze: 'class Node: def __init__(self, val): self.val = val; self.next = None; head = Node(1); head.next = Node(2); head.next.next = Node(3); result = head.next.val'. What is result, and evaluate how linked lists enable dynamic sizing?

  1. The result is 2 because head points to first node (value 1), head.next points to second node (value 2), and head.next.val retrieves the second node's value, demonstrating how linked lists chain nodes via pointers, enabling O(1) insertion/deletion at known positions despite O(n) random access
  2. The result is 1 because head.next.val evaluates the leftmost object in the attribute chain first and returns immediately, so the expression is treated as equivalent to head.val and the .next hop is effectively ignored during evaluation
  3. The result is 3 because head.next.next was the last assignment executed in the code, and Python attribute chains always resolve to whichever node object was most recently created in the sequence rather than the one the dotted path actually names
  4. The result is 2, but linked lists do not add dynamic sizing benefits here since Python's built-in list type already grows and shrinks automatically, making the Node class's self.next pointer redundant for resizing a collection at runtime

Answer: A. The result is 2 because head points to first node (value 1), head.next points to second node (value 2), and head.next.val retrieves the second node's value, demonstrating how linked lists chain nodes via pointers, enabling O(1) insertion/deletion at known positions despite O(n) random access

ExplanationThe result is 2. The three Node objects form a chain: head refers to Node(1), whose .next refers to Node(2), whose .next refers to Node(3). So head.val is 1, head.next.val is 2, and head.next.next.val is 3. This particular line follows two pointer hops directly (head.next.val) rather than looping through the list, so no traversal of the whole structure happens here. Linked lists enable dynamic sizing because each node holds a reference to the next node instead of relying on one contiguous memory block: adding or removing a node just rewires a .next pointer, which is O(1) at a known position, unlike an array-based structure that may need to shift elements or reallocate. The tradeoff is that reaching an arbitrary node still requires walking node-by-node from the head, which is O(n) in the worst case for a full traversal.
← Set 4Set 6 →