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 1

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

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

Question 1 · Web Dev, APIs, SQL, Graphs, ML · hard

Consider the following scenario and evaluate: The Document Object Model (DOM) represents an HTML page as a tree of nodes. When JavaScript modifies the DOM 1000 times in a loop (e.g., appending 1000 list items), why does this cause poor performance?

  1. JavaScript is single-threaded, so 1000 DOM calls block the main thread for 1000 event loop cycles
  2. The browser's garbage collector runs after each DOM modification, causing 1000 GC pauses
  3. Each DOM modification triggers a layout recalculation (reflow) and repaint — the fix is batching the changes into a DocumentFragment and appending it once
  4. Each appendChild call makes an HTTP request to the server to validate the new DOM structure

Answer: C. Each DOM modification triggers a layout recalculation (reflow) and repaint — the fix is batching the changes into a DocumentFragment and appending it once

ExplanationDOM modifications are expensive because the browser may recalculate CSS styles, layout positions (reflow), and pixel painting (repaint) after each change. 1000 appends = potentially 1000 reflows. DocumentFragment is an in-memory container that batches changes — you build the entire subtree first, then append the fragment once, triggering only a single reflow/repaint cycle. This reduces layout thrashing from O(n) reflows to O(1), which is critical for smooth 60fps rendering.

Question 2 · Recursion and Dynamic Programming · hard

You're writing a recursive function to compute the nth Fibonacci number: ```python def fib(n): if n <= 1: return n return fib(n-1) + fib(n-2) ``` Calling 'fib(40)' takes ~30 seconds. What causes this extreme slowdown, and what is the time complexity?

  1. O(2ⁿ) — the function recomputes the same subproblems exponentially many times; fib(38) is computed twice, fib(37) three times, etc.
  2. O(n²) — each recursive call spawns two child calls, and n × n = n² total function calls
  3. O(n) — there are only n unique Fibonacci values to compute, so the function visits each once
  4. O(n log n) — assuming memoization automatically applies to all recursive calls, halving the work at each level like merge sort, when in fact naive recursion recomputes every subproblem from scratch

Answer: A. O(2ⁿ) — the function recomputes the same subproblems exponentially many times; fib(38) is computed twice, fib(37) three times, etc.

ExplanationFirst, the naive recursive Fibonacci has time complexity O(2ⁿ) because each call branches into TWO sub-calls, creating an exponential call tree. For fib(40), the number of function calls exceeds 300 million. Then, the key waste: fib(38) is computed 2 times, fib(37) is computed 3 times, fib(30) is computed thousands of times. The fix is memoization or dynamic programming, which reduces it to O(n). Option B incorrectly multiplies — two branches don't mean n². Option C would be true WITH memoization, but not for the naive version. Finally, option D misapplies merge-sort analysis — the Fibonacci tree is NOT balanced (left subtree is always deeper than right).

Question 3 · Database Indexing · hard

Consider the following scenario and evaluate: You query a database with 50,000 rows: '''sql SELECT name, score FROM students WHERE score > 90 ORDER BY score DESC; ''' The query takes 8 seconds without an index. After adding 'CREATE INDEX idx_score ON students(score);', it takes 0.02 seconds. Why does the index improve performance so dramatically?

  1. The index pre-sorts all 50,000 rows alphabetically by name, so the WHERE clause can skip non-matching names
  2. The index caches the entire 'students' table in RAM, so subsequent queries read from memory instead of disk
  3. The B-tree index on 'score' lets the database jump directly to rows where score > 90 using binary-search-like traversal, avoiding a full table scan of all 50,000 rows
  4. The index compresses the 'score' column to use less storage, which reduces the number of disk reads needed

Answer: C. The B-tree index on 'score' lets the database jump directly to rows where score > 90 using binary-search-like traversal, avoiding a full table scan of all 50,000 rows

ExplanationFirst, a B-tree index on 'score' organizes values in a balanced tree structure. To find score > 90, the database traverses the tree to the first value > 90 (O(log n) ≈ 16 comparisons for 50,000 rows), then scans sequentially from there. Then, without the index, the database must read ALL 50,000 rows (full table scan). The ORDER BY also benefits because the index stores values in sorted order. Option B is wrong — indexes don't cache tables; that's the buffer pool's job. Option C correctly describes this optimization. Finally, option D is wrong — indexes don't compress data; they create a separate lookup structure.

Question 4 · JavaScript Event Loop · hard

What does this async JavaScript code output? ```javascript console.log('1'); setTimeout(() => console.log('2'), 0); Promise.resolve().then(() => console.log('3')); console.log('4'); ``` What happens when you evaluate the output?

  1. 1, 4, 2, 3 — because setTimeout(0) is treated as running immediately after the synchronous code, ahead of the Promise's then() callback
  2. 1, 2, 3, 4 — because JavaScript is assumed to run every statement strictly in the order it's written, treating setTimeout and .then() as inline calls
  3. 1, 3, 4, 2 — because .then() is assumed to run synchronously the instant Promise.resolve() is called, firing before the final console.log('4') executes
  4. 1, 4, 3, 2 — because synchronous code runs first (1, 4), then microtasks (Promise callback: 3), then macrotasks (setTimeout: 2) in the event loop

Answer: D. 1, 4, 3, 2 — because synchronous code runs first (1, 4), then microtasks (Promise callback: 3), then macrotasks (setTimeout: 2) in the event loop

ExplanationJavaScript's event loop has a strict priority order: (1) Synchronous code on the call stack executes first, so '1' and '4' log immediately. (2) The microtask queue (Promises, queueMicrotask, MutationObserver) runs next, so '3' logs. (3) The macrotask queue (setTimeout, setInterval, I/O) runs last, so '2' logs. Even though setTimeout has a 0ms delay, it goes to the macrotask queue, which always runs only after the microtask queue is fully drained. This ordering is specified in the HTML spec's event loop processing model. Promise chains always complete before any timer fires in the same tick, which is exactly why the output is 1, 4, 3, 2.

Question 5 · Fetch API Error Handling · hard

Analyze the behavior of this fetch API error handling code: '''javascript async function getUser(id) { try { const response = await fetch(`/api/users/${id}`); if (!response.ok) { return { error: `Request failed with status ${response.status}` }; } return await response.json(); } catch (error) { return { error: 'Network error: ' + error.message }; } } ''' What happens when getUser() is called and the server responds with a 500 Internal Server Error, versus when it is called while the device has no internet connection at all?

  1. Both cases reach the catch block, because fetch() rejects its returned promise whenever the HTTP response indicates any kind of failure, including 4xx and 5xx status codes
  2. For the 500 response, the `if (!response.ok)` branch returns `{ error: 'Request failed with status 500' }` directly inside the try block; for no internet connection, fetch() itself rejects with a TypeError, so execution jumps to the catch block instead
  3. Neither case reaches the catch block, because `response.ok` being false causes fetch() to automatically retry the request up to three times before giving up silently and returning undefined
  4. For the 500 response, `response.json()` throws a SyntaxError because the server's error page isn't valid JSON, which is caught; for no internet connection, the function hangs indefinitely since fetch() has no built-in timeout mechanism

Answer: B. For the 500 response, the `if (!response.ok)` branch returns `{ error: 'Request failed with status 500' }` directly inside the try block; for no internet connection, fetch() itself rejects with a TypeError, so execution jumps to the catch block instead

ExplanationFor the 500 response, fetch() itself resolves successfully — the browser did receive a real HTTP response from the server, so the promise settles normally rather than rejecting. Inside the try block, response.ok is false (only status codes 200-299 are considered "ok"), so the code takes the `if (!response.ok)` branch and returns `{ error: 'Request failed with status 500' }` immediately, using the template literal to correctly interpolate response.status as 500. The catch block is never entered for this case. For no internet connection, there is no server to respond at all, so fetch() cannot establish a connection; it rejects its own promise with a TypeError (commonly with the message "Failed to fetch"). This sends execution straight to the catch block, which returns `{ error: 'Network error: ' + error.message }`. The rule this tests: fetch() only rejects on network-level failures — DNS lookup failure, no connection, CORS blocks — and never on HTTP status codes, even 404 or 500. Checking `response.ok` inside the try block is the only way to detect HTTP-level errors, since they never trigger the catch block on their own.

Question 6 · Binary Search Tree · hard

Analyze this Binary Search Tree insertion: ```python class BSTNode: def __init__(self, val): self.val = val self.left = self.right = None def insert(root, val): if root is None: return BSTNode(val) if val < root.val: root.left = insert(root.left, val) elif val > root.val: root.right = insert(root.right, val) return root def inorder(root): if not root: return [] return inorder(root.left) + [root.val] + inorder(root.right) root = None for v in [5, 3, 7, 1, 4, 6, 8]: root = insert(root, v) print(inorder(root)) ``` What is the output?

  1. [5, 3, 7, 1, 4, 6, 8] — BST inorder traversal preserves insertion order, not sorted order, because traversal respects insertion sequence
  2. [8, 7, 6, 5, 4, 3, 1] — inorder traversal on BST produces reverse-sorted order since larger values are stored on right subtrees
  3. [1, 3, 4, 5, 6, 7, 8] — because BST insert places smaller values left and larger right: 5(root) → 3 left → 7 right → 1 left-left → 4 left-right → 6 right-left → 8 right-right. Inorder traversal (left-root-right) yields sorted output
  4. [1,3,4,5,6,7,8] but with duplicates removed — BST inorder traversal eliminates duplicate values automatically during traversal

Answer: C. [1, 3, 4, 5, 6, 7, 8] — because BST insert places smaller values left and larger right: 5(root) → 3 left → 7 right → 1 left-left → 4 left-right → 6 right-left → 8 right-right. Inorder traversal (left-root-right) yields sorted output

ExplanationFirst, BST insertion for [5,3,7,1,4,6,8]: 5 becomes root. 3<5 → left of 5. Then, 7>5 → right of 5. 1<5,<3 → left of 3. 4<5,>3 → right of 3. 6>5,<7 → left of 7. 8>5,>7 → right of 7. Inorder traversal (Left-Root-Right) visits: left subtree of 5 first → [left of 3=1, root 3, right of 3=4], then root 5, then right subtree → [left of 7=6, root 7, right of 7=8]. Result: [1,3,4,5,6,7,8] — sorted! This is the BST invariant: inorder traversal ALWAYS produces sorted output. This property makes BSTs ideal for ordered data operations: range queries, finding min/max, and predecessor/successor lookups — all in O(h) time where h is tree height.

Question 7 · Merge Sort Analysis · hard

Trace this merge sort on the input [8, 3, 5, 1, 9, 2, 7, 4]:\n'''python\ndef merge_sort(arr):\n if len(arr) <= 1:\n return arr\n mid = len(arr) // 2\n left = merge_sort(arr[:mid])\n right = merge_sort(arr[mid:])\n return merge(left, right)\n\ndef merge(left, right):\n result = []\n i = j = 0\n while i < len(left) and j < len(right):\n if left[i] <= right[j]:\n result.append(left[i]); i += 1\n else:\n result.append(right[j]); j += 1\n return result + left[i:] + right[j:]\n'''\nCounting every comparison made inside the while loop across the entire run, how many total comparisons does this call to merge_sort make on this array?

  1. 17 comparisons — the four base merges of single elements (like [8] with [3]) each take exactly 1 comparison, the two merges of the resulting pairs each take 3 comparisons, and the final merge of the two 4-element halves takes 7 comparisons, so 1+1+3+1+1+3+7 = 17
  2. 24 comparisons — since n log₂n = 8 × log₂(8) = 8 × 3 = 24, and this formula gives the precise comparison count for any array of 8 elements, not just an upper bound on it
  3. 7 comparisons — only the last merge, which combines the two fully-sorted 4-element halves into the final 8-element result, actually compares elements; the earlier recursive calls just split and return the array without doing any comparison work
  4. 21 comparisons — merge_sort makes exactly 7 separate calls to merge() while sorting 8 elements, and since the final call performs 3 comparisons, every one of those 7 calls must also perform 3 comparisons each, giving 7 × 3 = 21

Answer: A. 17 comparisons — the four base merges of single elements (like [8] with [3]) each take exactly 1 comparison, the two merges of the resulting pairs each take 3 comparisons, and the final merge of the two 4-element halves takes 7 comparisons, so 1+1+3+1+1+3+7 = 17

ExplanationTrace it level by level. Splitting phase: [8,3,5,1,9,2,7,4] splits into [8,3,5,1] and [9,2,7,4], each splitting again down to single elements — no comparisons happen during splitting, only during merging. Merging back up: Level 1 (pairs of single elements) — merge([8],[3]) compares 8<=3 (false), appends 3 then 8: 1 comparison. merge([5],[1]) compares 5<=1 (false): 1 comparison. merge([9],[2]): 1 comparison. merge([7],[4]): 1 comparison. Level 2 (pairs of size-2 lists) — merge([3,8],[1,5]) needs 3 comparisons to produce [1,3,5,8]. merge([2,9],[4,7]) needs 3 comparisons to produce [2,4,7,9]. Level 3 (final merge of the two size-4 halves) — merge([1,3,5,8],[2,4,7,9]) takes 7 comparisons to interleave into [1,2,3,4,5,7,8,9]. Total: 1+1+1+1+3+3+7 = 17 comparisons, confirmed by executing the exact code above. This also explains the O(n log n) bound: there are log₂(8)=3 merge levels, and each level's merges together touch at most all 8 elements, giving an upper bound of n×log₂n = 24 comparisons — but the true count is 17 because every merge stops comparing as soon as one side empties, after which the rest is appended for free with no more comparisons.

Question 8 · Promise.finally() · hard

What does Promise.finally() accomplish in this code? ```javascript fetch('/api/data') .then(response => response.json()) .then(data => console.log('Success:', data)) .catch(error => console.error('Error:', error)) .finally(() => console.log('Request complete')); ``` What happens when you evaluate the output?

  1. 'Request complete' only logs on success — finally() only executes if the promise resolves, not on rejection
  2. 'Request complete' logs but can be suppressed by returning false — finally() can prevent subsequent handlers from executing
  3. 'Request complete' always logs — finally() runs after the promise settles (resolves or rejects), guaranteeing execution. Unlike .then() (only on resolve) or .catch() (only on reject), finally() runs regardless of outcome, making it ideal for cleanup
  4. 'Request complete' logs twice if an error occurs — finally() executes both as cleanup and as an additional.catch() handler when errors happen

Answer: C. 'Request complete' always logs — finally() runs after the promise settles (resolves or rejects), guaranteeing execution. Unlike .then() (only on resolve) or .catch() (only on reject), finally() runs regardless of outcome, making it ideal for cleanup

ExplanationPromise.finally(callback) runs its callback once the promise settles, whether it resolves or rejects, and receives no value or error — unlike .then() (runs only on resolve) or .catch() (runs only on reject). If the fetch succeeds, .then() logs 'Success: ...' and finally() then logs 'Request complete'. If it fails instead, .catch() logs 'Error: ...' and finally() still logs 'Request complete'. This guaranteed execution makes finally() ideal for cleanup work — closing connections, hiding loading spinners — that must happen regardless of how the promise resolves.

Question 9 · BST Search · hard

What does this BST search and what is its time complexity? ```python def search(node, target): if node is None: return False if target == node.value: return True elif target < node.value: return search(node.left, target) else: return search(node.right, target) # Search for 2 in BST with 5, 3, 7, 1, 9, 2 ``` What happens when you evaluate the output?

  1. Returns True, O(log n) average case; average balanced BST has height O(log n) so search visits O(log n) nodes; worst case O(n) for unbalanced BSTs becoming linked lists
  2. Returns True, but the time complexity is always O(n) because every recursive call must check both left and right subtrees before returning
  3. Returns False, because 2 does not equal 5, 3, or 7, so the search terminates at the first three comparisons without reaching the correct branch
  4. Returns True, O(1) constant time, because BST search always locates the target in exactly one comparison at the root

Answer: A. Returns True, O(log n) average case; average balanced BST has height O(log n) so search visits O(log n) nodes; worst case O(n) for unbalanced BSTs becoming linked lists

ExplanationBST search compares the target to node.value at each step: equal returns True, less means all matches must be in the left subtree (go left), greater means go right. Tracing target=2 in the tree built from insertions 5, 3, 7, 1, 9, 2: start at 5, since 2<5 go left to 3; since 2<3 go left to 1; since 2>1 go right, landing on 2, which matches, so search returns True. Time complexity depends on tree height, since each recursive call descends exactly one level: a balanced BST has height O(log n), so search visits O(log n) nodes on average; an unbalanced BST (for example, nodes inserted in sorted order) can degenerate into a linked list of height O(n), making search O(n) in the worst case. This is why self-balancing trees like AVL and Red-Black trees guarantee O(log n) height regardless of insertion order.

Question 10 · Loss Functions · hard

What is the role of the loss function in neural network training? ```python # Loss function example: Mean Squared Error (MSE) def mse_loss(predicted, actual): return sum((predicted[i] - actual[i]) ** 2 for i in range(len(predicted))) / len(predicted) y_true = [1, 2, 3] y_pred = [1.1, 2.2, 2.9] loss = mse_loss(y_pred, y_true) print(loss) ``` What does this measure and why is it important?

  1. Assumes all loss functions are equivalent, missing that they encode different assumptions about errors and outlier importance
  2. Thinks loss function choice doesn't affect optimization, missing that different losses have different gradient landscapes
  3. Measures prediction error: loss = 0.02 — quantifies how far predictions are from actual values; backpropagation uses the gradient of loss to update weights; lower loss = better model
  4. Believes squared error is always optimal, missing that it's sensitive to outliers and absolute error, Huber loss, or others fit better

Answer: C. Measures prediction error: loss = 0.02 — quantifies how far predictions are from actual values; backpropagation uses the gradient of loss to update weights; lower loss = better model

ExplanationFirst, loss function quantifies prediction error. MSE = mean of squared differences = ((1.1-1)² + (2.2-2)² + (2.9-3)²) / 3 = (0.01 + 0.04 + 0.01) / 3 = 0.06 / 3 = 0.02. Then, goal: minimize loss by adjusting weights. Backpropagation computes ∂loss/∂weight (gradient), then updates weight -= learning_rate × gradient. Each epoch, loss decreases (ideally). Other loss functions: (1) Binary CrossEntropy for classification (0/1), (2) Categorical CrossEntropy for multi-class, (3) MAE (Mean Absolute Error) for regression. Choosing loss matters: MSE penalizes large errors heavily (outlier-sensitive), MAE treats all errors equally. During training, monitor loss: if loss plateaus, learning rate is too low or stuck in local minimum; if loss oscillates, learning rate is too high.

Question 11 · JavaScript Closures and Scope · hard

Block scope vs function scope: var declared inside if/for block escapes to function scope; let/const stay in block. Code: for (var i=0; i<3; i++) { /* empty */ }; console.log(i);. How would you predict what it log, and why?

  1. Logs 0 because var retains its initial value from before the loop started; the loop's increments only update a local copy of i inside the block, so the outer console.log still sees the original value.
  2. Logs undefined because var-declared loop variables are garbage-collected once the for-block exits, leaving the outer binding uninitialized until it is accessed again.
  3. Throws ReferenceError because all variables declared inside a for-loop are block-scoped regardless of var/let/const keyword
  4. Logs 3. var i is hoisted to function scope, so every loop iteration updates that same single variable; when the loop condition fails at i=3, that final value persists, and console.log(i) runs in the same function scope where i is still visible, so it logs 3.

Answer: D. Logs 3. var i is hoisted to function scope, so every loop iteration updates that same single variable; when the loop condition fails at i=3, that final value persists, and console.log(i) runs in the same function scope where i is still visible, so it logs 3.

Explanationvar i is hoisted to the top of the enclosing function scope, not the for-block, so every iteration of the loop (i=0, then 1, then 2) updates that one single variable rather than creating a new binding each time. The loop exits when the condition i<3 fails, which happens once i has been incremented to 3, and that final value of 3 remains in the variable after the loop ends. Because console.log(i) executes in the same function scope where var i lives, it prints 3. Had the loop used let i instead, i would be scoped to the for-block itself; once the loop finished, that binding would no longer exist outside the block, so console.log(i) would throw a ReferenceError instead. This is exactly why let is generally preferred over var for loop counters—it keeps the variable from leaking into the surrounding scope.

Question 12 · Time/Space Complexity · hard

Analyze the time complexity of an iterative binary search algorithm on a sorted array of 10,000 elements. What is the maximum number of iterations required compared to linear search, and why does this matter for large datasets?

  1. 100-200 iterations while linear search requires 5,000 iterations because each iteration eliminates one quarter of remaining elements
  2. 50 iterations while linear search requires 10,000 iterations because binary search checks only 0.5% of the array before finding any element
  3. Binary search requires 13-14 iterations while linear search requires up to 10,000 iterations because binary search divides the search space in half each time
  4. the same iterations as linear search because both must check every element in worst case scenarios

Answer: C. Binary search requires 13-14 iterations while linear search requires up to 10,000 iterations because binary search divides the search space in half each time

ExplanationBinary search has O(log n) time complexity, so for 10,000 elements it needs about log₂(10,000) ≈ 13.3, which rounds up to a maximum of 14 iterations in the worst case, since each comparison discards half of the remaining elements. Linear search has O(n) complexity, requiring up to 10,000 iterations because in the worst case it must check every element one at a time until it finds a match. This gap matters for large datasets because it grows with array size: doubling the array to 20,000 elements adds only one more iteration to binary search (log₂(20,000) ≈ 14.3) while linear search's worst case doubles to 20,000, so binary search stays fast even as datasets scale far beyond 10,000 elements.

Question 13 · Recursion · hard

Given a recursive function that calculates Fibonacci numbers where F(n) = F(n-1) + F(n-2), trace the execution path for F(5) and evaluate the total number of function calls made versus an iterative approach?

  1. For F(5), the recursive approach makes 5 function calls and the iterative approach makes 15 calls, because recursion automatically optimizes repeated calculations.
  2. Both approaches make exactly 15 function calls because they compute the same mathematical sequence regardless of implementation style.
  3. In this case, the recursive approach makes 15 function calls for F(5) while the iterative approach makes only 5 calls, demonstrating why recursion causes exponential overhead for Fibonacci problems.
  4. Overall, the recursive approach makes 32 function calls while the iterative approach makes only 1 call, but recursion is always preferred for readability.

Answer: C. In this case, the recursive approach makes 15 function calls for F(5) while the iterative approach makes only 5 calls, demonstrating why recursion causes exponential overhead for Fibonacci problems.

ExplanationFirst, f(5) = F(4) + F(3) creates a recursion tree where F(4) is calculated once, F(3) twice, F(2) three times, F(1) five times, and F(0) three times, totaling 15 calls. This exponential growth O(2^n) demonstrates why recursion inefficiently recalculates values. Then, an iterative approach computes each value once in linear O(n) time with only 5 iterations, showing this is critical optimization because without memoization, F(30) requires 2,692,537 calls.

Question 14 · Sorting Algorithms · hard

Calculate the number of comparisons required by bubble sort to sort an array of 100 elements in the worst case, and compare this with merge sort's guaranteed maximum comparisons. Why does this difference impact sorting algorithm selection?

  1. Bubble sort requires 10,000 comparisons while merge sort requires exactly 100 comparisons because merge sort always beats bubble sort in all scenarios
  2. Both algorithms require identical 100 comparisons because they must verify order of all elements
  3. Bubble sort requires 100×99/2 = 4,950 comparisons in worst case, while merge sort requires 100 × log₂(100) ≈ 664 comparisons maximum, making merge sort approximately 7.5 times more efficient for large datasets
  4. Merge sort requires 50,000 comparisons while bubble sort requires only 4,950 comparisons, proving bubble sort superior

Answer: C. Bubble sort requires 100×99/2 = 4,950 comparisons in worst case, while merge sort requires 100 × log₂(100) ≈ 664 comparisons maximum, making merge sort approximately 7.5 times more efficient for large datasets

ExplanationBubble sort is O(n²), so its worst-case comparison count is n(n-1)/2 = 100×99/2 = 4,950. Merge sort is O(n log n), so its maximum comparisons are about 100 × log₂(100) ≈ 100 × 6.64 = 664. Dividing 4,950 by 664 gives roughly 7.5, so merge sort needs about 7.5 times fewer comparisons than bubble sort here. This is exactly why the difference matters for algorithm selection: as the input size n grows, an O(n²) comparison count grows far faster than an O(n log n) one, so the efficiency gap only widens, making merge sort the far more scalable choice for large datasets.

Question 15 · Async JavaScript (Promises, async/await) · hard

Analyze a JavaScript Promise chain with three asynchronous operations: fetch user data (200ms), process data (150ms), and display results (50ms). Calculate total execution time and compare with async/await syntax. Why does execution order matter for user experience?

  1. Promise chains and async/await both take 200ms total because JavaScript's event loop runs fetch user data, process data, and display results as microtasks that all resolve on the same tick, regardless of each operation's stated duration.
  2. Async/await executes fetch user data, process data, and display results in parallel, so total time equals the longest single operation, 200ms, because the await keyword tells the JavaScript engine to run all subsequent statements concurrently on separate threads.
  3. Promise chain execution is sequential: 200ms (fetch) + 150ms (process) + 50ms (display) = 400ms total, because process data depends on the result returned by fetch user data, so it cannot start until fetching completes. Async/await produces the same 400ms total execution time as the Promise chain, since it does not change what runs when — it only makes the sequential control flow easier to read and debug, which improves user experience by making the 400ms wait predictable rather than by reducing it.
  4. Promise chains complete 1000ms faster than async/await because each .then() callback fires immediately when the previous Promise is created, before fetch user data, process data, or display results actually finish executing.

Answer: C. Promise chain execution is sequential: 200ms (fetch) + 150ms (process) + 50ms (display) = 400ms total, because process data depends on the result returned by fetch user data, so it cannot start until fetching completes. Async/await produces the same 400ms total execution time as the Promise chain, since it does not change what runs when — it only makes the sequential control flow easier to read and debug, which improves user experience by making the 400ms wait predictable rather than by reducing it.

ExplanationSequential operations execute one after another: 200ms (fetch) + 150ms (process) + 50ms (display) = 400ms total wait, because process data cannot begin until fetch user data returns its result — there is no independent step here to overlap. Async/await produces the exact same 400ms execution time as the Promise chain; rewriting .then() chains as await statements changes only how the control flow reads (linear statements instead of nested callbacks, with try/catch for errors), not the order or duration of the underlying operations. Execution order still matters for user experience because the 400ms is a real, unavoidable wait the user perceives — showing a loading state or breaking the work into smaller feedback steps are the actual ways to make that wait feel shorter, not parallelizing steps that depend on each other's output.

Question 16 · HTML5 and Semantic Web · hard

A developer rebuilds a blog page, replacing generic &lt;div&gt; wrappers with semantic HTML5: &lt;nav&gt; for the site menu, &lt;article&gt; for each post, &lt;time datetime="2026-08-12"&gt;Aug 12, 2026&lt;/time&gt; for the publish date, and &lt;figure&gt;/&lt;figcaption&gt; for an image with its caption. A screen reader and a search-engine crawler now both need to build an outline of the page and extract the correct publish date without relying on CSS classes or visual layout. Why does this semantic version give them more usable information than the original all-&lt;div&gt; page?

  1. Semantic tags such as <nav>, <article>, and <time> function only as CSS styling hooks, so screen readers and search-engine crawlers parse them identically to generic <div> elements and gain no additional structural or metadata information from the tag names themselves.
  2. Browsers automatically assign extra ranking weight to any page containing five or more semantic tags, so the improvement comes from Google's indexing algorithm favoring tag count rather than from screen readers or crawlers extracting different information from the markup.
  3. <nav> and <article> map to implicit ARIA landmark roles that let a screen reader jump straight to the menu or a given post, while <time datetime="2026-08-12"> supplies a locale-independent ISO date a machine can parse unambiguously — unlike the visible text "8/12/2026," which readers outside the US would read as 12 August; a generic <div> carries none of these roles or machine-readable values no matter how it is styled.
  4. The <figure> and <figcaption> tags remove the image and caption from the accessibility tree entirely, forcing screen readers to skip past them, which is why developers use these tags to speed up navigation for assistive technology.

Answer: C. <nav> and <article> map to implicit ARIA landmark roles that let a screen reader jump straight to the menu or a given post, while <time datetime="2026-08-12"> supplies a locale-independent ISO date a machine can parse unambiguously — unlike the visible text "8/12/2026," which readers outside the US would read as 12 August; a generic <div> carries none of these roles or machine-readable values no matter how it is styled.

ExplanationSemantic elements carry meaning beyond their visual style. Because <nav> and <article> have implicit ARIA landmark roles built into the HTML5 spec, assistive technology exposes them in the accessibility tree as jump-to targets — a screen reader user can go directly to the site menu or to one post without reading through every line of visible text, something a generic <div>, which has no implicit role, cannot offer. The <time datetime="2026-08-12"> element separates the human-readable label from a machine-readable value: the datetime attribute is a fixed ISO-8601 string, so a crawler or calendar tool reads exactly one date no matter how the visible text is formatted. That resolves a real ambiguity in something like "8/12/2026," which US readers take as August 12 but most other readers would take as 12 August — an ambiguity a <div> does nothing to fix. The claim that semantic tags are purely stylistic and parse identically to <div> ignores that HTML5 tag names are exposed as distinct roles in the accessibility tree, not just CSS hooks. The claim about an automatic ranking boost tied to counting five-plus semantic tags confuses correlation with mechanism — any indexing benefit comes from crawlers extracting cleaner structure and metadata, not from a rule that rewards tag quantity. And <figure>/<figcaption> do not remove content from the accessibility tree; they group an image with its caption so assistive technology presents them together, the opposite of skipping them.

Question 17 · Web Security (CORS, JWT, OAuth) · hard

A React app hosted at https://shop.example.com runs this code to load order data from a payments API: ```javascript fetch("https://api.payments.example.com/orders") .then(res => res.json()) .then(data => console.log(data)); ``` The payments server receives this simple GET request, processes it normally, and sends back a JSON response — but its response does not include an `Access-Control-Allow-Origin` header. Given this, what actually happens, and why does this show that CORS alone cannot secure the API?

  1. The browser refuses to send the request to api.payments.example.com at all once it notices the origins do not match, so the payments server never receives or processes the order request in the first place
  2. The browser sends the request over the network exactly as usual and the payments server processes it and returns the data, but the browser then blocks the page's JavaScript from reading that response body because the required header is missing — since this check happens only inside browsers, the identical request sent with curl or from a mobile app would still succeed and return the full data
  3. Because the Access-Control-Allow-Origin header is missing, the payments server itself detects the mismatched origin and rejects the request before doing any processing, making CORS function as a server-side authentication check equivalent to verifying an API key
  4. The missing header causes the browser to block the response only when the request method is GET, while POST and DELETE requests are always let through by the browser regardless of what CORS headers the server sends back

Answer: B. The browser sends the request over the network exactly as usual and the payments server processes it and returns the data, but the browser then blocks the page's JavaScript from reading that response body because the required header is missing — since this check happens only inside browsers, the identical request sent with curl or from a mobile app would still succeed and return the full data

ExplanationA plain GET fetch like this one is a "simple request," so the browser does not hold it back or ask permission first — it goes out over the network, the payments server processes it normally, and a response comes back with the JSON data attached. CORS enforcement happens only on the receiving end, inside the browser: once the response arrives, the browser checks for an Access-Control-Allow-Origin header that permits shop.example.com, and finding none, it blocks the page's own JavaScript from reading the response body. The data was fetched successfully; only the script's access to it was denied. This reveals the core limitation: CORS is a rule that browsers agree to follow, not a barrier the server enforces against the outside world. A request made with curl, a mobile app, a server-to-server call, or Postman never goes through a browser's CORS check at all, so it receives the full response regardless of any Access-Control-Allow-Origin header. That is why an API that truly needs protection cannot rely on CORS headers as its defense — it needs real authentication (like JWTs or OAuth tokens) that is checked on every request, independent of who or what is making it.

Question 18 · SQL (SELECT, JOIN, GROUP BY, subqueries) · hard

A grade-9 project stores completed and pending purchases in this `orders` table: | order_id | customer_id | amount | status | |----------|-------------|--------|-----------| | 1 | C1 | 200 | completed | | 2 | C1 | 150 | completed | | 3 | C2 | 300 | completed | | 4 | C2 | 100 | pending | | 5 | C3 | 400 | completed | | 6 | C3 | 250 | completed | | 7 | C3 | 100 | completed | | 8 | C1 | 50 | pending | This query is run against the table: ```sql SELECT customer_id, COUNT(*) AS cnt, SUM(amount) AS total FROM orders WHERE status = 'completed' GROUP BY customer_id HAVING COUNT(*) >= 2 ORDER BY total DESC; ``` Which customer_id rows appear in the result, and in what order?

  1. Only two customers survive the filters — C3 appears first with a total of 750, then C1 with a total of 350, since C2's single completed order (count = 1) fails the HAVING COUNT(*) >= 2 test.
  2. Sorting puts C1 ahead of C3 in the output — C1 (total 350) first, then C3 (total 750) — because ORDER BY total DESC is assumed to place the smallest totals at the top.
  3. All three customers make it into the output — C3 (750), C1 (350), and C2 (300) — on the reasoning that WHERE alone already removes any customer failing the order-count requirement, leaving nothing for HAVING to filter.
  4. C1 gets dropped from the output entirely, leaving C3 (750) first and C2 (300) second, under the mistaken idea that GROUP BY discards one of C1's two completed rows before COUNT(*) is evaluated.

Answer: A. Only two customers survive the filters — C3 appears first with a total of 750, then C1 with a total of 350, since C2's single completed order (count = 1) fails the HAVING COUNT(*) >= 2 test.

ExplanationWHERE status = 'completed' runs first and removes the two pending rows (order 4 for C2, order 8 for C1), leaving six rows: C1-200, C1-150, C2-300, C3-400, C3-250, C3-100. GROUP BY customer_id then forms three groups: C1 has 2 rows summing to 350, C2 has 1 row summing to 300, C3 has 3 rows summing to 750. HAVING COUNT(*) >= 2 is evaluated after grouping and checks each group's row count, not its total: C1's count of 2 and C3's count of 3 both pass, but C2's count of 1 fails, so that entire group is dropped and its total of 300 never reaches the output. GROUP BY itself never discards or merges away individual rows from a passing group — it only combines rows for aggregation, so both of C1's completed orders are counted. Finally, ORDER BY total DESC sorts the two surviving groups from the largest total to the smallest, putting C3 (750) ahead of C1 (350).

Question 19 · Machine Learning basics · hard

Consider a machine learning dataset with 10,000 samples and 20 features where a logistic regression model achieves 72% accuracy on training data and 68% accuracy on validation data. Evaluate whether this accuracy gap suggests overfitting and recommend whether the model is production-ready (analyze the algorithm output)?

  1. A 4% gap between training and validation accuracy indicates severe overfitting and the model must be discarded immediately, since any train-validation gap above 0% signals the model has memorized noise in the training data rather than learned generalizable patterns.
  2. The model is production-ready because 68% validation accuracy is acceptable for all applications regardless of use case, since accuracy alone is a sufficient metric for deployment decisions in any domain.
  3. Accuracy gap: 72% training vs 68% validation = 4% difference suggests mild overfitting (acceptable threshold is 2-5%). Model shows reasonable generalization. However, production readiness depends on use case: for fraud detection (98% precision required), 68% is too low. For recommendation system (70% acceptable), 68% is borderline acceptable. F1-score matters more than accuracy: precision-recall trade-off determines true performance.
  4. Accuracy gap is irrelevant because training accuracy determines real-world model performance

Answer: C. Accuracy gap: 72% training vs 68% validation = 4% difference suggests mild overfitting (acceptable threshold is 2-5%). Model shows reasonable generalization. However, production readiness depends on use case: for fraud detection (98% precision required), 68% is too low. For recommendation system (70% acceptable), 68% is borderline acceptable. F1-score matters more than accuracy: precision-recall trade-off determines true performance.

ExplanationFirst, overfitting evaluation: acceptable train-validation gap is roughly 1-3% for well-regularized models, 3-5% for normal models, and above 5% typically signals overfitting. At a 4% gap, this model shows mild overfitting but is not severely overfit. Second, accuracy assessment: whether 68% validation accuracy is adequate is use-case dependent rather than a fixed cutoff. For medical diagnosis (detect disease): 68% sensitivity is too low (misses 32% of cases). For an email spam filter: 68% true positive rate is inadequate (too much spam reaches the inbox). For fraud detection needing near-98% precision: 68% is far too low. For a recommendation system where roughly 70% accuracy is typically acceptable: 68% is borderline acceptable. Production readiness therefore depends on the deployment context rather than a single blanket number: for high-stakes, precision-critical applications this model is not ready, while for lower-stakes applications like recommendations it sits close to acceptable. A full readiness check should also weigh (1) F1-score, since the precision-recall trade-off matters more than raw accuracy, (2) error analysis for systematic bias, and (3) a planned A/B test before full deployment. Improvement strategy: increasing regularization (e.g., L2 lambda around 0.05) would likely narrow the gap and lift validation accuracy slightly, and feature engineering could yield further gains. Finally, post-deployment: monitor for accuracy degradation due to distribution shift, since real-world data differs from training data.

Question 20 · Array Methods · hard

Consider the following JavaScript code and trace through its execution: '''javascript const nums = [10, 1, 2, 21, 3]; const sorted = nums.sort(); console.log(sorted); ''' What gets logged, and why?

  1. [1, 10, 2, 21, 3] — with no compare function, sort() converts each element to a string and orders them by comparing UTF-16 code units (lexicographic order) rather than numeric value: "1" < "10" < "2" < "21" < "3"; to sort numbers correctly you must pass a compare function like (a, b) => a - b
  2. [1, 2, 3, 10, 21] — sort() always compares elements by their actual numeric value whenever every element in the array is a number, so no compare function is needed for arrays of numbers
  3. [21, 10, 3, 2, 1] — calling sort() with no arguments sorts the array in descending order by default; ascending order only happens when you explicitly pass (a, b) => a - b
  4. sort() throws a TypeError at runtime because JavaScript requires an explicit compare function whenever an array contains a number with more than one digit

Answer: A. [1, 10, 2, 21, 3] — with no compare function, sort() converts each element to a string and orders them by comparing UTF-16 code units (lexicographic order) rather than numeric value: "1" < "10" < "2" < "21" < "3"; to sort numbers correctly you must pass a compare function like (a, b) => a - b

ExplanationArray.prototype.sort() with no compare function does NOT sort numbers by value — it converts every element to a string with String(x) and orders those strings by UTF-16 code unit (lexicographic/dictionary) comparison. For nums = [10, 1, 2, 21, 3], the string forms are "10", "1", "2", "21", "3". Comparing them character by character: "1" is a prefix of "10", so "1" < "10"; comparing "10" and "2" looks only at the first character ('1' vs '2'), so "10" < "2"; similarly "2" < "21" (prefix rule) and "21" < "3" (first character '2' vs '3'). That gives the string order "1" < "10" < "2" < "21" < "3", which maps back to the numeric array [1, 10, 2, 21, 3] — verified directly in Node.js. This is one of JavaScript's most common gotchas: sort() is NOT numeric by default for any input type, it is always string-based unless you supply a compare function such as (a, b) => a - b (ascending) or (a, b) => b - a (descending). It also mutates the original array in place and returns a reference to that same array, rather than creating a sorted copy.
Set 2 →