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

Grade 9 AI & Computer Science Practice Questions — Set 5

20 questions from the Grade 9 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 · Quicksort Partition · hard

```python def partition(arr, low, high): pivot = arr[high] i = low - 1 for j in range(low, high): if arr[j] <= pivot: i += 1 arr[i], arr[j] = arr[j], arr[i] arr[i+1], arr[high] = arr[high], arr[i+1] return i + 1 arr = [8, 3, 1, 5, 2] idx = partition(arr, 0, 4) print(arr, idx) ``` What does this code print?

  1. [1, 2, 8, 5, 3] 2 — the pivot 2 ends up at index 2 after partitioning
  2. [1, 2, 8, 5, 3] 1 — pivot is arr[4]=2; elements <=2 are 1,2; after partition: [1, 2, | 8, 5, 3] with pivot at index 1
  3. [1, 2, 3, 5, 8] 1 — partition fully sorts the array and returns the pivot index
  4. [2, 3, 1, 5, 8] 0 — the pivot goes to the beginning of the array

Answer: B. [1, 2, 8, 5, 3] 1 — pivot is arr[4]=2; elements <=2 are 1,2; after partition: [1, 2, | 8, 5, 3] with pivot at index 1

ExplanationPivot = arr[4] = 2. i starts at -1. j=0: arr[0]=8 > 2, skip. j=1: arr[1]=3 > 2, skip. j=2: arr[2]=1 <= 2, i=0, swap arr[0] and arr[2] → [1, 3, 8, 5, 2]. j=3: arr[3]=5 > 2, skip. After loop: swap arr[i+1]=arr[1] with arr[4] → [1, 2, 8, 5, 3]. Return i+1 = 1. Pivot 2 is now at index 1, with all elements <=2 to its left and all elements >2 to its right, exactly as the Lomuto partition scheme guarantees.

Question 82 · Binary Search · hard

def binary_search(arr, target): lo, hi = 0, len(arr) - 1 while lo <= hi: mid = (lo + hi) // 2 if arr[mid] == target: return mid elif arr[mid] < target: lo = mid + 1 else: hi = mid - 1 return -1 arr = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91] print(binary_search(arr, 23)) What is the output?

  1. 5 — 23 is at index 5; trace: lo=0,hi=9,mid=4(16<23)→lo=5; lo=5,hi=9,mid=7(56>23)→hi=6; lo=5,hi=6,mid=5(23==23)→return 5
  2. 4 — mid starts at 4 and 16 is close to 23 so it returns 4
  3. 6 — binary search checks mid=5 first which is 23 but adjusts +1 for 0-indexing correction
  4. -1 — 23 is not found because the algorithm skips over it during the narrowing

Answer: A. 5 — 23 is at index 5; trace: lo=0,hi=9,mid=4(16<23)→lo=5; lo=5,hi=9,mid=7(56>23)→hi=6; lo=5,hi=6,mid=5(23==23)→return 5

Explanationarr has 10 elements (indices 0-9). Iteration 1: lo=0, hi=9, mid=(0+9)//2=4, arr[4]=16 < 23, so lo=5. Iteration 2: lo=5, hi=9, mid=(5+9)//2=7, arr[7]=56 > 23, so hi=6. Iteration 3: lo=5, hi=6, mid=(5+6)//2=5, arr[5]=23 == 23, return 5. The target is found at index 5 after just 3 iterations — binary search's halving strategy (10 → 5 → 2 remaining elements) locates it in at most ceil(log2(10)) = 4 comparisons, far fewer than the 6 a linear scan of this array would need.

Question 83 · Inversion Counting · hard

```python def count_inversions(arr): count = 0 for i in range(len(arr)): for j in range(i+1, len(arr)): if arr[i] > arr[j]: count += 1 return count print(count_inversions([5, 3, 1, 4, 2])) ``` What is the output?

  1. 7 — inversions are: (5,3),(5,1),(5,4),(5,2),(3,1),(3,2),(4,2) = 7 pairs where left > right
  2. 10 — every pair is an inversion in a nearly reversed list of 5 elements
  3. 5 — one inversion per element in the array
  4. 4 — only adjacent pairs that are out of order count as inversions

Answer: A. 7 — inversions are: (5,3),(5,1),(5,4),(5,2),(3,1),(3,2),(4,2) = 7 pairs where left > right

ExplanationCheck all pairs (i,j) where i<j and arr[i]>arr[j]: (5,3)✓ (5,1)✓ (5,4)✓ (5,2)✓ (3,1)✓ (3,4)✗ (3,2)✓ (1,4)✗ (1,2)✗ (4,2)✓. Total = 7 inversions. Note: max inversions for 5 elements would be C(5,2)=10 (fully reversed). This array has 7 out of that possible 10.

Question 84 · Stack-Based Bracket Validation · hard

What is the output of this code? def is_balanced(s): stack = [] pairs = {')': '(', ']': '[', '}': '{'} for ch in s: if ch in '([{': stack.append(ch) elif ch in pairs: if not stack or stack[-1] != pairs[ch]: return False stack.pop() return len(stack) == 0 print(is_balanced('({[]})')) print(is_balanced('({[}])')) print(is_balanced('((()')) What is printed?

  1. True then False then False — '({[]})' is properly nested; '({[}])' has a mismatched } at position 4 — the stack top at that point is '[', which needs ']' to close it, not '}'; '((()' has 2 unmatched opening parens left on stack
  2. True then True then False — both first and second strings have matching counts of each bracket type
  3. True then False then True — '((()' is balanced because it contains a complete '()' pair inside
  4. False then False then False — none of these strings are balanced because they mix different bracket types

Answer: A. True then False then False — '({[]})' is properly nested; '({[}])' has a mismatched } at position 4 — the stack top at that point is '[', which needs ']' to close it, not '}'; '((()' has 2 unmatched opening parens left on stack

ExplanationString 1 '({[]})': the algorithm pushes '(', '{', '[' onto the stack, then each closing bracket matches the top of the stack in reverse order — ']' pops '[', '}' pops '{', ')' pops '(' — leaving the stack empty, so the function returns True. String 2 '({[}])': '(', '{', '[' are pushed the same way, but the 4th character '}' is checked against the stack top '[', and since pairs['}'] is '{' while stack[-1] is '[', the condition stack[-1] != pairs[ch] is true, so the function returns False immediately without processing the remaining ']' and ')'. String 3 '((()': three '(' are pushed, then ')' pops one of them, leaving two unmatched '(' on the stack when the loop ends; since len(stack) is 2 rather than 0, the function returns False. The three print statements therefore output True, False, and False in that order.

Question 85 · Insertion Sort Analysis · hard

What does this code print? ```python def insertion_sort(arr): comparisons = 0 for i in range(1, len(arr)): key = arr[i] j = i - 1 while j >= 0 and arr[j] > key: comparisons += 1 arr[j + 1] = arr[j] j -= 1 if j >= 0: comparisons += 1 arr[j + 1] = key return comparisons data = [4, 1, 3, 2] print(insertion_sort(data)) print(data) ``` Trace through the loop carefully and predict both output lines — what gets printed?

  1. 6 then [1, 2, 3, 4] — inserting 1 costs one shift comparison, inserting 3 costs one shift plus one stopping comparison against 1, and inserting 2 costs two shift comparisons plus one stopping comparison against 1, for a running total of 1, 3, 6, while the list is sorted in place
  2. 4 then [1, 2, 3, 4] — the while loop's body only increments comparisons on iterations where a shift actually happens, so counting just those shift events across all three insertions gives 1 + 1 + 2 = 4, with the array still ending up sorted
  3. 7 then [1, 2, 3, 4] — every insertion, including the very first one where the key shifts past all preceding elements, ends by hitting a stopping comparison against some arr[j], so three insertions each add one extra count on top of the four shifts
  4. 6 then [4, 1, 3, 2] — the comparison total is right, but since arr is a parameter name local to the function, modifying it inside insertion_sort cannot change the list object that data refers to in the caller

Answer: A. 6 then [1, 2, 3, 4] — inserting 1 costs one shift comparison, inserting 3 costs one shift plus one stopping comparison against 1, and inserting 2 costs two shift comparisons plus one stopping comparison against 1, for a running total of 1, 3, 6, while the list is sorted in place

ExplanationTrace comparisons is initialized to 0 and the array starts as [4, 1, 3, 2]. For i=1, key=1 and j=0. The while condition checks arr[0]=4 > 1, which is true, so comparisons becomes 1, 4 shifts right, and j becomes -1. The loop now stops because j < 0, so the post-loop check (if j >= 0) does not add anything. Key 1 is placed at index 0: array is [1, 4, 3, 2]. Running total: 1. For i=2, key=3 and j=1. The while condition checks arr[1]=4 > 3, true, so comparisons becomes 2, 4 shifts right, j becomes 0. The while condition checks arr[0]=1 > 3, false, so the loop exits with j=0 still >= 0. The post-loop check fires, adding one more: comparisons becomes 3. Key 3 is placed at index 1: array is [1, 3, 4, 2]. Running total: 3. For i=3, key=2 and j=2. The while condition checks arr[2]=4 > 2, true, so comparisons becomes 4, 4 shifts right, j becomes 1. The while condition checks arr[1]=3 > 2, true, so comparisons becomes 5, 3 shifts right, j becomes 0. The while condition checks arr[0]=1 > 2, false, so the loop exits with j=0 still >= 0. The post-loop check fires, adding one more: comparisons becomes 6. Key 2 is placed at index 1: array is [1, 2, 3, 4]. Running total: 6. The function returns 6, and since arr is the same list object as data (Python passes the reference, and every update uses in-place indexing like arr[j+1] = arr[j]), the caller's data list is also sorted. The two printed lines are 6 and [1, 2, 3, 4]. The count of 4 undercounts by ignoring that a loop which exits because arr[j] is no longer greater than key still performed one real comparison — the failing one — which the post-loop check exists specifically to record. The count of 7 overcounts by assuming that check always fires, but when the key shifts all the way past every earlier element (as happens for the very first insertion here), j ends at -1 and there is no arr[j] left to compare against, so no stopping comparison is recorded for that insertion.

Question 86 · Polymorphism and Inheritance · hard

Consider this Python code: ```python class Animal: def __init__(self, name): self.name = name def speak(self): return f'{self.name} makes a sound' class Dog(Animal): def speak(self): return f'{self.name} barks' class Cat(Animal): pass animals = [Dog('Rex'), Cat('Whiskers'), Animal('Bird')] for a in animals: print(a.speak()) ``` What is the output?

  1. The output is: Rex barks, then Whiskers makes a sound, then Bird makes a sound.
  2. Rex makes a sound then Whiskers makes a sound then Bird makes a sound — all use the base class method
  3. Rex barks then Error — Cat has no speak() method defined and cannot inherit from Animal
  4. Rex barks then Whiskers barks then Bird makes a sound — Cat inherits from Dog not Animal

Answer: A. The output is: Rex barks, then Whiskers makes a sound, then Bird makes a sound.

ExplanationDog.speak() overrides Animal.speak(), returning 'Rex barks'. Cat has no speak() method, so Python looks up the MRO to Animal.speak(), returning 'Whiskers makes a sound'. Animal('Bird').speak() returns 'Bird makes a sound'. This is polymorphism: the same method call .speak() produces different results depending on the actual class of the object. Cat inherits everything from Animal since it uses pass.

Question 87 · Iterative DFS · hard

Trace through this code and determine what it prints. def dfs_iterative(graph, start): visited = [] stack = [start] while stack: node = stack.pop() if node not in visited: visited.append(node) for neighbor in reversed(graph[node]): if neighbor not in visited: stack.append(neighbor) return visited g = {'A': ['B', 'C'], 'B': ['D', 'E'], 'C': ['F'], 'D': [], 'E': [], 'F': []} print(dfs_iterative(g, 'A')) What is printed?

  1. ['A', 'B', 'D', 'E', 'C', 'F'] — DFS visits all 6 nodes (indices 0-5) using stack: pop A, push C then B (reversed); pop B, push E then D; pop D (leaf); pop E (leaf); pop C, push F; pop F. Visited order: A,B,D,E,C,F
  2. ['A', 'C', 'F', 'B', 'E', 'D'] — this is what results if the reversed() call is omitted: neighbors get pushed in listed order (B then C), so C ends up on top of the stack and is popped immediately after A, before B is ever visited.
  3. ['A', 'B', 'C', 'D', 'E', 'F'] — this is the order breadth-first search (BFS) would produce instead of DFS, visiting all of A's direct neighbors before going deeper, which for this graph happens to match alphabetical order.
  4. The reversed() call is sometimes misread as reversing the final visited list rather than each neighbor list before pushing, which would incorrectly suggest an output of ['F', 'C', 'E', 'D', 'B', 'A'].

Answer: A. ['A', 'B', 'D', 'E', 'C', 'F'] — DFS visits all 6 nodes (indices 0-5) using stack: pop A, push C then B (reversed); pop B, push E then D; pop D (leaf); pop E (leaf); pop C, push F; pop F. Visited order: A,B,D,E,C,F

ExplanationTrace: stack=[A]. Pop A, visited=[A], push reversed(['B','C'])=['C','B'], stack=[C,B]. Pop B, visited=[A,B], push reversed(['D','E'])=['E','D'], stack=[C,E,D]. Pop D, visited=[A,B,D], no neighbors, stack=[C,E]. Pop E, visited=[A,B,D,E], no neighbors, stack=[C]. Pop C, visited=[A,B,D,E,C], push reversed(['F'])=['F'], stack=[F]. Pop F, visited=[A,B,D,E,C,F]. The reversed() call ensures each node's neighbors are pushed in reverse order, so the first-listed neighbor lands on top of the stack and is popped first — giving the same left-to-right traversal order as recursive DFS.

Question 88 · Run-Length Encoding · hard

Consider the following function: ```python def compress(s): if not s: return '' result = [] count = 1 for i in range(1, len(s)): if s[i] == s[i-1]: count += 1 else: result.append(s[i-1] + str(count)) count = 1 result.append(s[-1] + str(count)) return ''.join(result) ``` What is the output of `print(compress('aaabbbccaa'))`?

  1. a3b3c2a2 — run-length encoding over the 10-char input 'aaabbbccaa': 'aaa'→a3, 'bbb'→b3, 'cc'→c2, 'aa'→a2; note the two separate 'a' runs produce 2 entries
  2. a5b3c2 — the algorithm is assumed to count all occurrences of each character regardless of position, combining both 'a' runs into a single count of 5
  3. a3b3c2 — this assumes the function returns before appending the count for the last group, so the trailing 'aa' never gets added to the result
  4. 3a3b2c2a — the count (like 3) comes before the character in standard RLE of the 10-char string

Answer: A. a3b3c2a2 — run-length encoding over the 10-char input 'aaabbbccaa': 'aaa'→a3, 'bbb'→b3, 'cc'→c2, 'aa'→a2; note the two separate 'a' runs produce 2 entries

ExplanationTrace through 'aaabbbccaa': i=1: a==a, count=2. i=2: a==a, count=3. i=3: b≠a, append 'a3', count=1. i=4: b==b, count=2. i=5: b==b, count=3. i=6: c≠b, append 'b3', count=1. i=7: c==c, count=2. i=8: a≠c, append 'c2', count=1. i=9: a==a, count=2. After loop: append 'a2'. Result: 'a3b3c2a2'. The algorithm treats consecutive runs independently — the two 'a' groups are separate because they are not adjacent.

Question 89 · Kadane Maximum Subarray · hard

Consider this Python code that finds the maximum subarray sum using Kadane's algorithm: ```python def max_subarray(arr): max_sum = current = arr[0] for x in arr[1:]: current = max(x, current + x) max_sum = max(max_sum, current) return max_sum print(max_subarray([-2, -3, 4, -1, -2, 1, 5, -3])) ``` What value does this code print?

  1. 7, since the contiguous subarray [4, -1, -2, 1, 5] produces the largest running sum found during the scan.
  2. 5, because the single largest element in the array exceeds every subarray sum computed around it.
  3. 10, from adding up every positive number in the array (4 + 1 + 5), even though those numbers are not contiguous.
  4. 4, since the algorithm's running sum resets to zero the instant it encounters any negative number.

Answer: A. 7, since the contiguous subarray [4, -1, -2, 1, 5] produces the largest running sum found during the scan.

ExplanationTrace the algorithm with max_sum = current = -2 (the first element). For x=-3: current = max(-3, -2 + -3) = max(-3, -5) = -3, so max_sum stays -2. For x=4: current = max(4, -3 + 4) = max(4, 1) = 4, so max_sum becomes 4. For x=-1: current = max(-1, 4 + -1) = 3, max_sum stays 4. For x=-2: current = max(-2, 3 + -2) = 1, max_sum stays 4. For x=1: current = max(1, 1 + 1) = 2, max_sum stays 4. For x=5: current = max(5, 2 + 5) = 7, so max_sum updates to 7. For x=-3: current = max(-3, 7 + -3) = 4, max_sum stays 7. The final answer is 7, matching the contiguous run [4, -1, -2, 1, 5] (4 - 1 - 2 + 1 + 5 = 7). The key insight is the line `current = max(x, current + x)`: the running sum only restarts at the current element when extending the previous run would make things worse, not simply whenever a negative number appears — that is why the dip through -1 and -2 stays inside the winning subarray instead of breaking it. Picking just the largest single number ignores that combining it with a nearby run can score higher, and summing all positive numbers ignores the requirement that the elements be contiguous.

Question 90 · Topological Sort · hard

Consider the following DFS-based topological sort implementation, run on the graph g: ```python def topological_sort(graph): visited = set() result = [] def dfs(node): if node in visited: return visited.add(node) for neighbor in graph.get(node, []): dfs(neighbor) result.append(node) for node in graph: dfs(node) return result[::-1] g = {'A': ['B'], 'B': ['C'], 'C': [], 'D': ['C']} print(topological_sort(g)) ``` What is the exact output printed?

  1. ['D', 'A', 'B', 'C'] — dfs(A) recurses into B then C, appending C, B, A in that post-order; D is then visited separately and appended after its own dfs(C) call returns immediately since C is already visited; reversing the post-order list [C, B, A, D] gives this final result
  2. ['C', 'B', 'A', 'D'] — this is the raw post-order list built by the recursive dfs() calls before the final result[::-1] reversal is applied, so printing it directly would skip a required step
  3. ['A', 'D', 'B', 'C'] — although this respects the edges A→B, B→C, and D→C, it is not the sequence this specific code actually produces, since dfs(A) is invoked and fully completes before dfs(D) ever runs
  4. RecursionError is raised — dfs(D) calls dfs(C) a second time and Python re-enters the same call frame indefinitely since C was already appended to result

Answer: A. ['D', 'A', 'B', 'C'] — dfs(A) recurses into B then C, appending C, B, A in that post-order; D is then visited separately and appended after its own dfs(C) call returns immediately since C is already visited; reversing the post-order list [C, B, A, D] gives this final result

ExplanationPython dict iteration follows insertion order, so the outer loop calls dfs('A') first, then dfs('B'), dfs('C'), dfs('D') — though only dfs('A') and dfs('D') do real work, since 'B' and 'C' get visited during A's recursion. dfs('A') recurses into 'B' (via graph.get('A', [])), which recurses into 'C' (via graph.get('B', [])); 'C' has no neighbors, so it is appended first, giving result=['C']. Returning up, 'B' is appended (result=['C','B']), then 'A' is appended (result=['C','B','A']). The outer loop's next calls, dfs('B') and dfs('C'), return immediately because both nodes are already in visited — a revisit simply triggers the early-return guard, not an error or infinite recursion. The call dfs('D') then runs: graph.get('D', []) = ['C'], so it calls dfs('C'), which returns immediately since 'C' is visited, and then 'D' is appended, giving result=['C','B','A','D']. Reversing this list with result[::-1] produces the printed output ['D','A','B','C']. This ordering respects every edge in the graph (A before B, B before C, D before C), but it is only one of several orderings that would be mathematically valid — a different valid ordering is not automatically what this exact code prints, since the output is fixed by the specific traversal order the code follows.

Question 91 · Min-Heap Insertion · hard

class MinHeap:\n def __init__(self):\n self.data = []\n def push(self, val):\n self.data.append(val)\n self._sift_up(len(self.data) - 1)\n def _sift_up(self, i):\n while i > 0:\n parent = (i - 1) // 2\n if self.data[i] < self.data[parent]:\n self.data[i], self.data[parent] = self.data[parent], self.data[i]\n i = parent\n else:\n break\n\nh = MinHeap()\nfor x in [5, 3, 8, 1, 4]:\n h.push(x)\nprint(h.data[0])\nprint(h.data)\n\nWhat does this code print?

  1. 1 then [1, 3, 8, 5, 4] — the min-heap invariant guarantees h.data[0]=1 (the minimum); after all insertions and sift-ups, the exact array is [1, 3, 8, 5, 4]
  2. 1 then [1, 2, 3, 4, 5] — a min-heap stores elements in fully sorted order
  3. 5 then [5, 3, 8, 1, 4] — push appends without reordering because _sift_up has a bug
  4. 3 then [3, 1, 8, 5, 4] — the first non-5 value pushed becomes the root

Answer: A. 1 then [1, 3, 8, 5, 4] — the min-heap invariant guarantees h.data[0]=1 (the minimum); after all insertions and sift-ups, the exact array is [1, 3, 8, 5, 4]

ExplanationTrace insertions: push(5): data=[5]. push(3): data=[5,3], sift_up: 3<5 → swap → [3,5]. push(8): data=[3,5,8], 8>3 → no swap. push(1): data=[3,5,8,1], sift_up: i=3, parent=1, 1<5 → swap → [3,1,8,5], i=1, parent=0, 1<3 → swap → [1,3,8,5]. push(4): data=[1,3,8,5,4], i=4, parent=1, 4>3 → stop. Final: [1,3,8,5,4]. Root (index 0) is always the minimum. The heap is NOT fully sorted — it only guarantees parent ≤ children. The heap-order property ensures correctness because the parent is always smaller than or equal to its children.

Question 92 · Grid Path Counting · hard

You're analyzing this recursive function: ```python def count_paths(grid, r, c): if r == 0 or c == 0: return 1 return count_paths(grid, r-1, c) + count_paths(grid, r, c-1) print(count_paths(None, 3, 3)) ``` What is the output?

  1. 20 — this counts unique paths in a 4×4 grid (indices 0-3) from top-left to (3,3) moving only right or down; equals C(6,3) = 20
  2. 6 — there are only 6 paths in a 3×3 grid
  3. 64 — each cell has 2 choices for 6 steps: 2^6 = 64
  4. 10 — the function counts paths in a 3×3 grid, which equals C(5,2) = 10

Answer: A. 20 — this counts unique paths in a 4×4 grid (indices 0-3) from top-left to (3,3) moving only right or down; equals C(6,3) = 20

Explanationcount_paths(None, 3, 3) counts paths from (0,0) to (3,3) in a grid, moving only down (r-1) or right (c-1). Base case: any cell in row 0 or column 0 has exactly 1 path. The total equals C(r+c, r) = C(6, 3) = 20. Verification: count_paths(3,3) = count_paths(2,3) + count_paths(3,2). The recursion builds Pascal's triangle: row/col 0 all 1s, each interior cell = sum of cell above + cell to the left. The grid of values: [1,1,1,1], [1,2,3,4], [1,3,6,10], [1,4,10,20]. Answer: 20.

Question 93 · Longest Common Prefix · hard

What does this code print? ```python def common_prefix(a, b): i = 0 while i < len(a) and i < len(b) and a[i] == b[i]: i += 1 return a[:i] def lcp(strs): if not strs: return '' prefix = strs[0] for s in strs[1:]: prefix = common_prefix(prefix, s) if prefix == '': return '' return prefix print(lcp(['classroom', 'classify', 'class'])) print(lcp(['apple', 'banana', 'cherry'])) ``` What are the two printed lines?

  1. The prints are 'class' then an empty string — comparing 'classroom' against 'classify' matches at the first five positions (c-l-a-s-s) and diverges at the sixth character ('r' vs 'i'), so common_prefix returns 'class'; comparing that against 'class' matches fully, keeping the prefix at 'class'. In the second call, 'apple' and 'banana' differ at the very first character ('a' vs 'b'), so common_prefix returns immediately, making prefix empty and triggering the early return before 'cherry' is even compared
  2. 'classi' (6 characters) then an empty string — the while loop keeps extending i as long as any of the three strings in each list share a character at that position, so 'classroom', 'classify', and 'class' are all compared together and agree through 'classi' before the seventh character breaks the match
  3. 'classroom' unchanged (all nine characters) then 'apple' unchanged — since prefix is initialised to the first string in the list and the while loop only compares a string against itself on the first pass, no trimming happens and the function returns the original first string in both calls
  4. 'class' paired with 'banana' as the results — the second call finds no shared prefix among 'apple', 'banana', and 'cherry', so the function falls back to returning the middle string in the list as a default value

Answer: A. The prints are 'class' then an empty string — comparing 'classroom' against 'classify' matches at the first five positions (c-l-a-s-s) and diverges at the sixth character ('r' vs 'i'), so common_prefix returns 'class'; comparing that against 'class' matches fully, keeping the prefix at 'class'. In the second call, 'apple' and 'banana' differ at the very first character ('a' vs 'b'), so common_prefix returns immediately, making prefix empty and triggering the early return before 'cherry' is even compared

ExplanationIn the first call, prefix starts as 'classroom'. Comparing it against 'classify' inside common_prefix, the two strings match at the first five positions — c, l, a, s, s — and disagree at the sixth character, where 'classroom' has 'r' and 'classify' has 'i'. So the inner while loop stops with i = 5, and common_prefix returns 'classroom'[:5], which is 'class'. That value becomes the new prefix. Comparing 'class' against 'class' next, every character matches all the way through (i reaches 5, the length of both strings), so the prefix stays 'class', and lcp returns 'class' for the first print. In the second call, prefix starts as 'apple'. Comparing it against 'banana', the very first characters already disagree ('a' vs 'b'), so the while loop body never executes and common_prefix returns 'apple'[:0], an empty string. Since prefix is now empty, the function returns '' immediately — 'cherry' is never even examined. So the second print shows an empty string. The key idea is that common_prefix only ever shrinks the prefix by comparing it pairwise against each next string, and a mismatch at position i means exactly i characters matched before that point.

Question 94 · Floyd Cycle Detection · hard

Trace Floyd's cycle detection algorithm on a linked list whose tail loops back into the middle of the list rather than to the head: ```python def detect_cycle(head): slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: return True return False class Node: def __init__(self, val): self.val = val self.next = None a, b, c, d, e = Node(1), Node(2), Node(3), Node(4), Node(5) a.next = b; b.next = c; c.next = d; d.next = e; e.next = c print(detect_cycle(a)) ``` What does this code print?

  1. True — the pointers converge at the node whose value is 4 after three loop iterations, since fast closes the gap on slow by one extra step each pass and must catch it somewhere along the 3→4→5→3 cycle
  2. False — because node c already has an incoming link from b, Python's slow == fast comparison always evaluates to False once a node has more than one predecessor
  3. It prints True, though the pointers actually meet at the node valued 3, since fast overtakes the cycle's entry point on its very first pass through the loop
  4. Running this raises an error, because e.next = c produces a circular reference that print() cannot safely traverse when displaying the function's result

Answer: A. True — the pointers converge at the node whose value is 4 after three loop iterations, since fast closes the gap on slow by one extra step each pass and must catch it somewhere along the 3→4→5→3 cycle

ExplanationThe list is 1→2→3→4→5 with the tail node e (value 5) looping back to c (value 3), forming the cycle 3→4→5→3. Tracing the pointers: after the first iteration slow sits at value 2 and fast at value 3 (no match); after the second iteration slow is at value 3 and fast is at value 5 (no match); after the third iteration slow advances to value 4 and fast also lands on value 4, so slow == fast fires and the function returns True. This works because once both pointers are inside the cycle, fast reduces its distance to slow by exactly one node per iteration, guaranteeing a collision without ever needing to know the cycle's length in advance. The comparison succeeds because slow and fast end up referencing the identical Node object, and Python's default object equality is identity-based, not dependent on how many other nodes point to it. Nothing here raises an error either — print() just displays the boolean that detect_cycle returns, since the algorithm is a simple iterative while loop that never attempts to print or recurse over the linked list itself.

Question 95 · 0/1 Knapsack Dynamic Programming · hard

Consider the following 0/1 knapsack dynamic programming code: def knapsack_01(weights, values, capacity): n = len(weights) dp = [[0] * (capacity + 1) for _ in range(n + 1)] for i in range(1, n + 1): for w in range(capacity + 1): dp[i][w] = dp[i-1][w] if weights[i-1] <= w: dp[i][w] = max(dp[i][w], dp[i-1][w - weights[i-1]] + values[i-1]) return dp[n][capacity] print(knapsack_01([2, 3, 4, 5], [3, 4, 5, 6], 5)) What is the output of this code?

  1. Value = 7 — optimal selection: items with weight 2 (value 3) and weight 3 (value 4) total weight 5, value 7; taking the item with weight 5 alone gives only 6
  2. 6 — take the single item with weight 5 and value 6
  3. 18 — sum of all four values (3+4+5+6=18), obtained by taking every item regardless of the weight capacity
  4. 5 — take only the item with weight 4 and value 5, since it is the single largest item that fits within capacity 5

Answer: A. Value = 7 — optimal selection: items with weight 2 (value 3) and weight 3 (value 4) total weight 5, value 7; taking the item with weight 5 alone gives only 6

ExplanationItems: (w=2,v=3), (w=3,v=4), (w=4,v=5), (w=5,v=6). Capacity=5. DP builds a table where dp[i][w] = max value using the first i items with capacity w. Key cells: dp[1][2]=3, dp[2][3]=4, dp[2][5]=7 (take both items 1 and 2: 2+3=5, 3+4=7), dp[3][4]=5, dp[3][5]=max(dp[2][5]=7, item3+dp[2][1]=5+0=5)=7, dp[4][5]=max(dp[3][5]=7, item4+dp[3][0]=6+0=6)=7. The optimal solution picks items 1 and 2 (weights 2+3=5, values 3+4=7), which beats taking the single item of weight 5 (value 6). The recurrence considers both including and excluding the current item because each item can either contribute to the optimal solution or not.

Question 96 · Sliding Window Maximum with Deque · hard

Consider this code: ```python def sliding_window_max(nums, k): from collections import deque dq = deque() result = [] for i, num in enumerate(nums): while dq and nums[dq[-1]] <= num: dq.pop() dq.append(i) if dq[0] <= i - k: dq.popleft() if i >= k - 1: result.append(nums[dq[0]]) return result print(sliding_window_max([1, 3, -1, -3, 5, 3, 6, 7], 3)) ``` What does this code print?

  1. [3, 3, 5, 5, 6, 7] — monotonic deque tracks max in each window of size 3: [1,3,-1]→3, [3,-1,-3]→3, [-1,-3,5]→5, [-3,5,3]→5, [5,3,6]→6, [3,6,7]→7
  2. [3, 5, 6, 7] — only values that are the overall maximum in some window appear
  3. [1, 3, 5, 5, 6, 7] — the first window starts at index 0 giving max 1
  4. [3, 3, 5, 5, 6, 7] but computed in O(nk) time — the deque provides no optimization

Answer: A. [3, 3, 5, 5, 6, 7] — monotonic deque tracks max in each window of size 3: [1,3,-1]→3, [3,-1,-3]→3, [-1,-3,5]→5, [-3,5,3]→5, [5,3,6]→6, [3,6,7]→7

ExplanationThe monotonic deque maintains indices in decreasing value order. Trace with k=3: i=0(1): dq=[0]. i=1(3): pop 0 (1≤3), dq=[1]. i=2(-1): dq=[1,2], i≥2 → result=[3]. i=3(-3): dq=[1,2,3], dq[0]=1 ≤ 3-3=0? No. result=[3,3]. i=4(5): pop 3,2,1 (all ≤5), dq=[4], result=[3,3,5]. i=5(3): dq=[4,5], result=[3,3,5,5]. i=6(6): pop 5,4, dq=[6], result=[3,3,5,5,6]. i=7(7): pop 6, dq=[7], result=[3,3,5,5,6,7]. The deque ensures O(n) total time because each index is pushed and popped at most once.

Question 97 · REST API Query Parameters · hard

You build a REST API for a school library system. A GET request to '/api/books?genre=science&grade=9' should return filtered results. Given that the server receives this request, what is the output of parsing the URL, and how would you access these filter parameters in Express.js?

  1. Query string 'genre=science&grade=9' (everything after '?') contains the filters. The server parses this into key-value pairs: {genre: 'science', grade: '9'}. In Express.js, these are accessed via req.query.genre and req.query.grade. Query parameters are the standard way to pass filters in GET requests without modifying the URL path
  2. Request body contains the filters as JSON. GET requests always send data in the body, and the '?' in the URL is just a separator with no special meaning for parameter passing
  3. URL path segments '/genre/science/grade/9' contain the filters. The server splits the URL by '/' to extract them as positional arguments from the route
  4. HTTP headers contain the filter data. The '?' and '&' characters in the URL are encoding artifacts that the browser converts to custom headers before sending the request

Answer: A. Query string 'genre=science&grade=9' (everything after '?') contains the filters. The server parses this into key-value pairs: {genre: 'science', grade: '9'}. In Express.js, these are accessed via req.query.genre and req.query.grade. Query parameters are the standard way to pass filters in GET requests without modifying the URL path

ExplanationIn a URL, everything after '?' is the query string. The '&' character separates multiple key-value pairs. So '?genre=science&grade=9' gives two parameters: genre=science and grade=9. In Express.js: req.query gives {genre: 'science', grade: '9'}. In Flask: request.args.get('genre'). Query strings are visible in the URL (unlike POST body data), making them ideal for filters, searches, and pagination in GET requests. They are also bookmarkable and cacheable.

Question 98 · BFS Traversal and Shortest Paths · hard

In a graph representing Indian railway connections, you run BFS starting from Delhi. The adjacency list is: Delhi→[Jaipur, Agra, Lucknow], Jaipur→[Delhi, Ahmedabad], Agra→[Delhi, Kanpur], Lucknow→[Delhi, Kanpur], Kanpur→[Agra, Lucknow], Ahmedabad→[Jaipur]. What is the BFS traversal order, and at what 'level' (minimum hops) is each city from Delhi?

  1. BFS order: Delhi, Jaipur, Agra, Lucknow, Ahmedabad, Kanpur. Level 0: Delhi. Level 1: Jaipur, Agra, Lucknow. Level 2: Ahmedabad, Kanpur. BFS explores all neighbors at distance d before moving to distance d+1, giving shortest hop counts in unweighted graphs
  2. BFS order: Delhi, Jaipur, Ahmedabad, Agra, Kanpur, Lucknow. All cities are at level 1 because BFS visits everything in one pass
  3. BFS order: Delhi, Lucknow, Kanpur, Agra, Jaipur, Ahmedabad. BFS always goes to the last neighbor first (like a stack), visiting the deepest path before backtracking
  4. BFS order: Delhi, Agra, Kanpur, Lucknow, Jaipur, Ahmedabad. BFS sorts neighbors alphabetically before visiting, ensuring deterministic traversal order

Answer: A. BFS order: Delhi, Jaipur, Agra, Lucknow, Ahmedabad, Kanpur. Level 0: Delhi. Level 1: Jaipur, Agra, Lucknow. Level 2: Ahmedabad, Kanpur. BFS explores all neighbors at distance d before moving to distance d+1, giving shortest hop counts in unweighted graphs

ExplanationBFS uses a queue (FIFO). Start: enqueue Delhi (level 0). Dequeue Delhi → enqueue its unvisited neighbors: Jaipur, Agra, Lucknow (all level 1, because they are directly connected). Dequeue Jaipur → enqueue Ahmedabad (level 2, since Delhi already visited). Dequeue Agra → enqueue Kanpur (level 2, because Delhi visited). Dequeue Lucknow → Kanpur already queued. Dequeue Ahmedabad → Jaipur visited. Dequeue Kanpur → all visited. This produces the result [Delhi, Jaipur, Agra, Lucknow, Ahmedabad, Kanpur] because BFS guarantees shortest path in unweighted graphs — Kanpur is exactly 2 hops from Delhi via either Agra or Lucknow.

Question 99 · XSS and Web Security · hard

You are building a web page for your school and write: 'document.getElementById("score").innerHTML = studentScore;'. If studentScore = '<script>alert("hacked")</script>', what happens when this code executes, and how would you evaluate and fix this security vulnerability?

  1. This creates a Cross-Site Scripting (XSS) vulnerability, though this exact payload is inert: browsers parse a <script> tag inserted via innerHTML but never execute it, by design. However, innerHTML still renders any other attacker-controlled markup as live HTML, so payloads like '<img src=x onerror=alert(1)>' or '<svg onload=alert(1)>' do execute. Prevention: use textContent instead of innerHTML (treats input as plain text, not HTML), or sanitize input with a library like DOMPurify
  2. No vulnerability exists because modern browsers automatically block all script tags inserted via innerHTML as part of the Content Security Policy
  3. The vulnerability is SQL injection — the script tag would be sent to the database and corrupt the school's student records
  4. This creates a buffer overflow because innerHTML cannot handle strings containing angle brackets, causing the browser to crash and potentially execute arbitrary code

Answer: A. This creates a Cross-Site Scripting (XSS) vulnerability, though this exact payload is inert: browsers parse a <script> tag inserted via innerHTML but never execute it, by design. However, innerHTML still renders any other attacker-controlled markup as live HTML, so payloads like '<img src=x onerror=alert(1)>' or '<svg onload=alert(1)>' do execute. Prevention: use textContent instead of innerHTML (treats input as plain text, not HTML), or sanitize input with a library like DOMPurify

ExplanationinnerHTML interprets the assigned string as HTML markup. When the HTML parser encounters a <script> element inserted this way, browsers intentionally refuse to execute it — per the HTML5 specification, script elements created via innerHTML are marked non-executing, so the exact alert("hacked") payload shown here would not actually fire. This is a common misconception, and the code is still a genuine XSS vulnerability: innerHTML will happily render and execute any other event-handler-based payload, such as '<img src=x onerror=alert(1)>' or '<svg onload=alert(1)>', neither of which uses a script tag at all. The fix is the same either way: use textContent (node.textContent = studentScore), which treats the value as plain text and never parses it as HTML, or use a sanitization library like DOMPurify when HTML rendering is genuinely required. XSS is ranked among the OWASP Top 10 web application vulnerabilities.

Question 100 · Dijkstra vs Bellman-Ford · hard

You implement Dijkstra's algorithm to find the shortest train route from Mumbai to Chennai. The graph has weighted edges (distances in km). After processing, the algorithm reports Mumbai→Pune→Hyderabad→Chennai = 1,280 km. Why can Dijkstra's NOT be used if some edges had negative weights (e.g., a discount route)?

  1. Dijkstra's fails with negative weights because it uses a greedy strategy: once a node is marked as "finalized" with its shortest distance, it is never reconsidered. A negative edge discovered later could provide a shorter path to an already-finalized node, but Dijkstra's would miss it. The Bellman-Ford algorithm handles negative weights by relaxing all edges V-1 times
  2. Dijkstra's actually works fine with negative weights — this is a common misconception. The priority queue handles negative values correctly
  3. Dijkstra's fails because negative weights cause the priority queue to enter an infinite loop, consuming all available memory until the program crashes
  4. Dijkstra's fails because distances cannot be negative in real life, so the algorithm was never designed to process such inputs and throws an exception

Answer: A. Dijkstra's fails with negative weights because it uses a greedy strategy: once a node is marked as "finalized" with its shortest distance, it is never reconsidered. A negative edge discovered later could provide a shorter path to an already-finalized node, but Dijkstra's would miss it. The Bellman-Ford algorithm handles negative weights by relaxing all edges V-1 times

ExplanationDijkstra's greedy approach finalizes nodes in order of increasing distance. Once a node is finalized, its shortest distance is set permanently. With negative edges, a later-discovered path through a negative edge could reduce an already-finalized distance — but Dijkstra's never rechecks finalized nodes. Example: A→B=5, A→C=2, C→B=-4. Dijkstra finalizes B at distance 5, but the actual shortest is A→C→B = 2+(-4) = -2. Bellman-Ford relaxes all edges V-1 times, catching these cases. It also detects negative cycles (infinite shortest path).
← Set 4Set 6 →