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 4

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

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

Question 61 · HTTP/2 multiplexing · hard

A browser needs to download 6 resources (3 images, 2 stylesheets, and 1 script) from the same server. Under HTTP/1.1, a single connection must return responses strictly in the order the requests were sent, so pages often open several parallel TCP connections instead. Under HTTP/2, the browser opens exactly one TCP connection and requests all 6 resources over it using multiplexing. If the server is slow to prepare the response for one image because of a delayed database query, what happens to the other 5 responses on that same HTTP/2 connection?

  1. All 5 other responses are blocked until the slow image response completes, because HTTP/2 still delivers only one response at a time per connection, in the same order requests were sent.
  2. The other 5 responses continue to arrive independently as their data becomes ready, because HTTP/2 splits each response into interleaved frames tagged with a stream ID so responses need not complete in request order.
  3. The connection resets entirely, forcing the browser to restart all 6 requests from scratch on a brand-new TCP connection once the slow image response finally arrives.
  4. HTTP/2 automatically opens five additional hidden TCP connections behind the scenes so the other resources are never affected by the slow one.

Answer: B. The other 5 responses continue to arrive independently as their data becomes ready, because HTTP/2 splits each response into interleaved frames tagged with a stream ID so responses need not complete in request order.

ExplanationHTTP/2 solves the head-of-line blocking problem that plagues a single HTTP/1.1 connection by multiplexing several independent streams over one TCP connection. Each request and response is broken into small frames, and every frame carries a stream ID showing which exchange it belongs to, so frames from different streams can be interleaved on the wire and reassembled independently at each end. This means the slow image simply has no frames to send yet — frames belonging to the other 5 streams keep flowing normally, and those responses finish as soon as their data is ready, with no need to wait for the image. That is precisely why HTTP/2 needs only one connection where HTTP/1.1 often needed several: instead of dodging in-order delivery by opening parallel connections, HTTP/2 fixes the ordering problem inside a single connection through stream IDs and frame interleaving, so a slow response degrades only itself, not the whole connection.

Question 62 · GraphQL resolver · hard

Consider this GraphQL resolver setup for a schema where each Author has a list of Posts: ```js const resolvers = { Query: { authors: async () => { const result = await db.query("SELECT * FROM authors"); return result.rows; // returns 50 author rows } }, Author: { posts: async (author) => { const result = await db.query( "SELECT * FROM posts WHERE author_id = $1", [author.id] ); return result.rows; } } }; ``` A client sends a single GraphQL query asking for all 50 authors together with each author's posts. With this resolver setup and no additional batching added, how many total database queries does the server execute to fulfill the request?

  1. 51 total queries: the top-level authors resolver executes once, and the field-level posts resolver then executes separately for each of the 50 authors returned, since GraphQL resolves each object's fields independently — giving 1 + 50 database round-trips, the classic N+1 problem.
  2. Exactly 2 total queries: one for authors and one for posts, since GraphQL automatically batches every field resolver of the same type into a single combined SQL call.
  3. 50 total queries only, since the top-level authors resolver is resolved by the GraphQL engine itself and does not count as a separate database round-trip.
  4. 1 total query, since the GraphQL schema's Author.posts relationship is automatically translated into a SQL join by the query planner before execution.

Answer: A. 51 total queries: the top-level authors resolver executes once, and the field-level posts resolver then executes separately for each of the 50 authors returned, since GraphQL resolves each object's fields independently — giving 1 + 50 database round-trips, the classic N+1 problem.

ExplanationGraphQL executes resolvers field by field, not as one combined query plan. The authors resolver runs once and returns an array of 50 author objects. Because each field on each returned object is resolved independently unless the server explicitly batches the calls (for example with a DataLoader), the engine then invokes the Author.posts resolver separately for every one of those 50 authors. That is 1 query for the author list plus 50 queries for posts, totaling 51 database round-trips. GraphQL does not auto-join across resolvers or auto-batch same-type fields on its own — unmanaged nested resolvers fetching related data one parent at a time is exactly the N+1 problem that GraphQL servers need dedicated batching to avoid.

Question 63 · WebRTC signaling · hard

A developer writes this WebRTC code:\n\nconst pc = new RTCPeerConnection(config);\npc.onicecandidate = e => {\n if (e.candidate) ws.send(JSON.stringify({ type: \"candidate\", candidate: e.candidate }));\n};\nconst offer = await pc.createOffer();\nawait pc.setLocalDescription(offer);\nws.send(JSON.stringify({ type: \"offer\", sdp: offer.sdp }));\n\nA classmate asks: \"pc is the WebRTC connection object — why does the code keep using ws.send() instead of having pc deliver the offer and ICE candidates straight to the other browser?\" Which explanation is correct?

  1. The WebRTC standard deliberately leaves signaling out of RTCPeerConnection: there is no P2P link yet when the offer and ICE candidates are created, so the app must relay this setup data through a separate channel it supplies itself, such as the WebSocket server, before any direct peer connection can form
  2. RTCPeerConnection is created with networking disabled by default, so ws.send() is required to request a temporary permission token from the browser; once granted, pc could send the offer and candidates directly
  3. The SDP text produced by createOffer() exceeds the maximum size WebRTC allows in a single message, so it must be broken into WebSocket frames and reassembled by the other browser before setLocalDescription can be called there
  4. onicecandidate encrypts each candidate as it fires, and only a WebSocket connection supports the cipher WebRTC requires, which is why candidates are sent through ws instead of through pc

Answer: A. The WebRTC standard deliberately leaves signaling out of RTCPeerConnection: there is no P2P link yet when the offer and ICE candidates are created, so the app must relay this setup data through a separate channel it supplies itself, such as the WebSocket server, before any direct peer connection can form

ExplanationWebRTC intentionally does not define how two browsers first find each other — that job is called "signaling," and the spec leaves it entirely to the application. Before pc.setLocalDescription(offer) and the later setRemoteDescription() on the other side complete, there is no peer-to-peer path at all, so the offer's SDP and each ICE candidate gathered by onicecandidate have nowhere to travel except an out-of-band channel the developer sets up — here, the WebSocket (ws). Once both sides have exchanged offer/answer and enough candidates, ICE connects the peers directly and further media/data flows through pc, not ws. The other options describe mechanisms WebRTC does not have: RTCPeerConnection has no "permission token" step gating direct sends, SDP has no such single-message size limit forcing frame-splitting, and ICE candidates are exchanged as plain JSON over whatever channel the app chooses — encryption is applied later, at the DTLS/SRTP layer once the connection is up, not by onicecandidate itself.

Question 64 · JWT authentication · hard

Look at this Express.js middleware:\n\nconst jwt = require(\"jsonwebtoken\");\nfunction auth(req, res, next) {\n const token = req.headers.authorization?.split(\" \")[1];\n if (!token) return res.status(401).json({ error: \"No token\" });\n try {\n req.user = jwt.verify(token, SECRET);\n next();\n } catch (e) {\n res.status(403).json({ error: \"Invalid token\" });\n }\n}\n\nA client intercepts a valid JWT and edits the payload section (changing \"role\": \"user\" to \"role\": \"admin\") by hand-editing the base64 text, then sends the modified token back WITHOUT knowing the server's SECRET. What happens when this request hits the auth middleware?

  1. jwt.verify() throws an error because the recomputed signature no longer matches the altered payload, so the catch block runs and the server responds 403 "Invalid token"
  2. jwt.verify() succeeds and accepts the edited "role": "admin" claim, because JWT payloads are only base64-encoded (not encrypted) and verify() merely decodes them without checking anything
  3. jwt.verify() succeeds but silently strips the tampered role field, so req.user ends up with the original "role": "user" value restored automatically
  4. jwt.verify() succeeds and next() runs, but the server logs a security warning because JWTs are designed to let any client edit their own claims as long as the header is unchanged

Answer: A. jwt.verify() throws an error because the recomputed signature no longer matches the altered payload, so the catch block runs and the server responds 403 "Invalid token"

ExplanationA JWT has three parts: header.payload.signature. The signature is computed as HMAC-SHA256(base64url(header) + \".\" + base64url(payload), SECRET) — it is a cryptographic seal over the exact bytes of the header and payload, not an encryption of them. Editing the payload (even by one character, like changing \"user\" to \"admin\") changes those bytes, so when jwt.verify() recomputes the HMAC-SHA256 signature from the (now-altered) header and payload and compares it to the signature attached to the token, the two no longer match. Because the client doesn't know SECRET, it cannot produce a matching signature for its edited payload. jwt.verify() therefore throws (a JsonWebTokenError for invalid signature), the middleware's catch(e) block runs, and the server correctly responds with 403 {\"error\": \"Invalid token\"} — the tampered request never reaches next(). The key misconception this question targets: JWT payloads being merely base64-encoded (readable, not secret) is true, but that does NOT mean they're unverified — the signature is what enforces integrity, independent of the payload being human-readable.

Question 65 · rate limiter sliding window · hard

Consider this JavaScript sliding-window rate limiter: ```js class RateLimiter { constructor(limit, windowMs) { this.limit = limit; this.windowMs = windowMs; this.requests = new Map(); } allow(clientId) { const now = Date.now(); const window = this.requests.get(clientId) || []; const valid = window.filter(t => now - t < this.windowMs); if (valid.length >= this.limit) return false; valid.push(now); this.requests.set(clientId, valid); return true; } } ``` A RateLimiter is created with `limit = 5`. One clientId already has 200 timestamps stored in its array from earlier calls made within the current window, and none of them have expired yet. When `allow()` is called again for this clientId, how many timestamps does the `filter()` call inspect before `allow()` returns?

  1. It inspects only 5 timestamps, because filter stops early once valid.length reaches the limit parameter, so the call runs in time proportional to the limit rather than the stored array size.
  2. Map.get() already returns just the timestamps inside the current window, so filter has zero elements left to inspect on this particular call.
  3. Array.prototype.filter always iterates every element of the array it is called on, so this call inspects all 200 stored timestamps regardless of what the limit value is.
  4. It inspects all 200 timestamps, yet the call still finishes in constant time because Map lookups are O(1) regardless of how large the stored array grows.

Answer: C. Array.prototype.filter always iterates every element of the array it is called on, so this call inspects all 200 stored timestamps regardless of what the limit value is.

ExplanationArray.prototype.filter has no early-exit condition — window.filter(t => now - t < this.windowMs) always walks the entire array from start to finish before it returns anything, so with 200 stored timestamps it inspects all 200 of them, producing a valid array of length 200 (since none have expired yet). Only after filter finishes does the code check valid.length >= this.limit; since 200 >= 5, allow() returns false. The limit parameter never influences how many elements filter visits during the scan — it is only compared against the result afterward. So the cost of an allow() call grows with however many timestamps are currently stored for that client, not with the configured limit, and not with the O(1) cost of Map.get()/Map.set() — those are constant-time only for locating the array itself, not for scanning everything inside it.

Question 66 · connection pool · hard

Study this connection-pool implementation: ```js class Pool { constructor(max = 10) { this.max = max; this.free = []; this.waiting = []; this.size = 0; } async acquire() { if (this.free.length) return this.free.pop(); if (this.size < this.max) { this.size++; return await createConnection(); } return new Promise(resolve => this.waiting.push(resolve)); } release(conn) { if (this.waiting.length) this.waiting.shift()(conn); else this.free.push(conn); } } ``` A program creates `const pool = new Pool(1)`, so at most one real connection may exist at a time. It calls `pool.acquire()` three times in a row. The very first call successfully creates and returns a connection, but no code has called `release()` yet. What happens to the second and third `acquire()` calls at this point?

  1. They throw a range error right away, since this.size has already reached this.max and the constructor set no way to hold extra requests.
  2. They each call createConnection() again, since the max value only limits objects stored in this.free, not how many connections can be created in total.
  3. They stay pending as unresolved promises, since this.size equals this.max and this.free is empty, so each call pushes its resolve function onto this.waiting instead of returning a connection.
  4. They immediately receive the same connection object the first call got, since this.free.pop() returns whichever connection was created most recently.

Answer: C. They stay pending as unresolved promises, since this.size equals this.max and this.free is empty, so each call pushes its resolve function onto this.waiting instead of returning a connection.

ExplanationWith new Pool(1), max is 1, so only one connection may exist. The first acquire() call finds this.free empty, but this.size (0) is less than this.max (1), so it increments size to 1 and awaits createConnection(), returning the pool's single connection. By the time the second acquire() runs, this.size (1) is no longer less than this.max (1), and this.free is still empty because nothing has been released, so both the free-array check and the size check fail and execution falls through to `return new Promise(resolve => this.waiting.push(resolve))` — the call's resolve function is stored in this.waiting and the promise stays unresolved. The third call behaves identically, so this.waiting ends up holding two resolve functions, each waiting for a future release(conn) call to hand it a connection through this.waiting.shift()(conn). No error is thrown, because the pool is built to queue callers rather than reject them once the limit is hit; no additional connection gets created beyond the first, because the size-versus-max guard blocks that path once size reaches max; and this.free.pop() is never reached at all, since this.free.length stays 0 throughout this sequence.

Question 67 · event sourcing pattern · hard

Consider this JavaScript class implementing the event sourcing pattern for a bank account: ```javascript class Account { constructor() { this.events = []; } deposit(amount) { this.events.push({ type: "deposit", amount }); } withdraw(amount) { this.events.push({ type: "withdraw", amount }); } getBalance() { return this.events.reduce((total, e) => { return e.type === "deposit" ? total + e.amount : total - e.amount; }, 0); } } const acc = new Account(); acc.deposit(500); acc.withdraw(200); acc.deposit(150); ``` What value does `acc.getBalance()` return, and how does that result reflect the core idea of event sourcing?

  1. It returns 450, because event sourcing never stores the balance itself — each deposit or withdraw is appended as an event, and getBalance() rebuilds the current state on demand by reducing over the full event log (0 + 500 − 200 + 150).
  2. It returns 850, since the reduce callback in getBalance() effectively adds every event's amount regardless of its type, meaning withdrawals inflate the balance the same way deposits do.
  3. It returns 150, because getBalance() reconstructs state from only the most recently pushed event rather than replaying the entire events array from the start.
  4. It throws a runtime error, because the class never assigns a this.balance property, so withdraw() has no existing stored value to subtract the amount from.

Answer: A. It returns 450, because event sourcing never stores the balance itself — each deposit or withdraw is appended as an event, and getBalance() rebuilds the current state on demand by reducing over the full event log (0 + 500 − 200 + 150).

ExplanationTracing the event log in order: the reducer starts at 0, adds 500 for the first deposit event to reach 500, subtracts 200 for the withdraw event to reach 300, then adds 150 for the second deposit event to reach a final balance of 450. This is exactly what distinguishes event sourcing from ordinary state storage: instead of holding a single mutable balance field that gets overwritten with each transaction, the class keeps an append-only log of what happened (events), and the current state is a derived value computed by replaying that log through reduce(). Because nothing is ever mutated in place, the same event log could be replayed to reconstruct the balance at any earlier point, or replayed after a bug fix to recompute corrected totals — the defining advantage of the pattern.

Question 68 · CQRS read model · hard

A read model for an e-commerce dashboard rebuilds its state by replaying events from an event store: ```js class OrderReadModel { constructor() { this.orders = new Map(); } apply(event) { if (event.type === "OrderPlaced") { this.orders.set(event.orderId, { status: "placed", total: event.total }); } else if (event.type === "OrderShipped") { const order = this.orders.get(event.orderId); if (order) order.status = "shipped"; } } } const model = new OrderReadModel(); for (const event of eventLog) { model.apply(event); } ``` If `eventLog` contains 50,000 events (a mix of `OrderPlaced` and `OrderShipped`), what is the total time complexity of running this `for` loop, and why?

  1. Replaying the events costs O(n^2) because apply() must scan every previously stored order to check for a duplicate orderId before it inserts or updates an entry.
  2. Each apply() call costs O(log n) because JavaScript's Map is implemented internally as a self-balancing binary search tree, so replaying all 50,000 events runs in O(n log n) time.
  3. Processing all 50,000 events costs O(n) because apply() performs exactly one constant-time Map.set or Map.get call per event, so n constant-time operations sum to a total that scales linearly with n.
  4. The entire loop costs O(1) because Map operations are constant-time regardless of collection size, so replaying 50,000 events takes the same fixed amount of work as replaying a handful.

Answer: C. Processing all 50,000 events costs O(n) because apply() performs exactly one constant-time Map.set or Map.get call per event, so n constant-time operations sum to a total that scales linearly with n.

ExplanationEach event in eventLog triggers exactly one call to apply(), and apply() does a single Map.set (for OrderPlaced) or a single Map.get followed by a direct property update (for OrderShipped) — both O(1) operations, since JavaScript's Map is backed by a hash table, not a linear scan or a search tree. Because the loop body performs one such constant-time step per event, running it 50,000 times costs 50,000 x O(1), which is O(n) overall. The scanning claim is wrong because apply() never walks through existing entries — it looks up orderId directly through the hash table. The binary-search-tree claim is wrong because Map lookups don't cost O(log n) in JavaScript engines; hashing gives O(1) access regardless of how many entries are stored. The constant-overall-time claim confuses the cost of one Map operation with the cost of repeating that operation n times in a loop: doing a fixed amount of work 50,000 times takes 50,000 times as long as doing it once, which is exactly what linear growth means.

Question 69 · consistent hashing · hard

Consider this implementation of consistent hashing, where getNode() uses binary search instead of a linear scan to find the right position on the hash ring:\n\nclass ConsistentHash {\n constructor(nodes, replicas = 100) {\n this.replicas = replicas;\n this.ring = new Map();\n this.sortedKeys = [];\n nodes.forEach(node => {\n for (let i = 0; i < replicas; i++) {\n const h = this.hash(node + \":\" + i);\n this.ring.set(h, node);\n this.sortedKeys.push(h);\n }\n });\n this.sortedKeys.sort((a, b) => a - b);\n }\n getNode(key) {\n const h = this.hash(key);\n let lo = 0, hi = this.sortedKeys.length - 1, idx = 0;\n while (lo <= hi) {\n const mid = Math.floor((lo + hi) / 2);\n if (this.sortedKeys[mid] >= h) { idx = mid; hi = mid - 1; }\n else { lo = mid + 1; }\n }\n return this.ring.get(this.sortedKeys[idx]);\n }\n}\n\nSuppose you build this ring with n = 8 physical nodes and replicas = 100 virtual copies per node, so the sortedKeys array holds m = 800 entries total. What is the time complexity of a single getNode() call, and roughly how many comparisons does it make in the worst case?

  1. O(log m), where m = n × replicas = 800 is the size of the sortedKeys array; binary search halves the remaining range each comparison, needing about ⌈log2(800)⌉ = 10 comparisons in the worst case
  2. O(n) = 8, because getNode() only ever needs to check one candidate per physical node to find the correct match
  3. O(m) = 800, because every call must linearly scan the full sortedKeys array to find the first key greater than or equal to the hash
  4. O(1), because the hash function computes each key's node directly, regardless of how many virtual replicas exist on the ring

Answer: A. O(log m), where m = n × replicas = 800 is the size of the sortedKeys array; binary search halves the remaining range each comparison, needing about ⌈log2(800)⌉ = 10 comparisons in the worst case

ExplanationgetNode() searches sortedKeys — the array of ALL virtual node positions on the ring, not just the physical nodes — using the classic binary-search-for-lower-bound pattern: at each step it looks at the midpoint, and if sortedKeys[mid] >= h it records idx and searches the left half (hi = mid - 1), otherwise it searches the right half (lo = mid + 1). Each comparison discards half the remaining candidates, which is exactly what makes binary search O(log m) rather than O(m). Here m = n × replicas = 8 × 100 = 800 total ring entries, so the loop runs at most ⌈log2(800)⌉ = 10 times before lo > hi and the loop exits (since 2^9 = 512 < 800 ≤ 1024 = 2^10). The physical node count n = 8 is irrelevant to the search cost — the algorithm never iterates node-by-node, it iterates over ring positions — so O(n) undercounts the real work. A plain O(m) = 800 linear scan would be correct only for a naive implementation using something like findIndex(), which checks every array element in order instead of halving the range; this code deliberately avoids that. And O(1) is wrong because hashing only locates the key's position on the ring (h = this.hash(key)) — finding which stored ring entry is the nearest one at or after that position still requires a search over the sorted array.

Question 70 · CSS specificity · hard

A CSS stylesheet applies these two rules to the same paragraph element `<p id="article" class="content article-text warning critical">Breaking news update</p>`:\n\nRule A: `#article.warning { color: red; }`\nRule B: `.content.article-text.warning.critical { color: orange; }`\n\nRule A has specificity 1-1-0 (one ID selector, one class selector). Rule B has specificity 0-4-0 (four class selectors, no ID selector). Both rules match the paragraph. Which color does the paragraph display, and why?

  1. Red — specificity is compared tier by tier starting with the ID count, so Rule A's single ID selector beats Rule B's four class selectors no matter how many classes are stacked, since class selectors can never outweigh an ID selector at that tier
  2. Orange — Rule B wins because four class selectors sum to a higher specificity score than one ID plus one class, since every selector type contributes equal weight toward a single combined total
  3. Orange — Rule B wins because it is written after Rule A in the stylesheet, and when two selectors match the same element the CSS cascade always applies whichever rule appears later, regardless of specificity
  4. Red — Rule A wins because ID attributes are unique per page, so any rule using an ID selector is automatically treated by the browser as an inline style and receives the same maximum specificity as the style attribute

Answer: A. Red — specificity is compared tier by tier starting with the ID count, so Rule A's single ID selector beats Rule B's four class selectors no matter how many classes are stacked, since class selectors can never outweigh an ID selector at that tier

ExplanationCSS specificity is a 3-part tuple (ID count, class count, element/type count) compared left to right — the parts are never added into one combined score. Rule A, `#article.warning`, has one ID selector and one class selector, giving specificity 1-1-0. Rule B, `.content.article-text.warning.critical`, has four class selectors and no ID, giving specificity 0-4-0. Comparing the tuples from the left: Rule A's ID count (1) beats Rule B's ID count (0) immediately, and the comparison stops right there — the class counts (1 vs 4) are never even reached. This is why one ID selector always outranks any number of class selectors: at the ID tier, 1 beats 0 outright regardless of what follows. So the browser applies Rule A, and the paragraph displays red. Source order is only used as a tie-breaker when two rules have the exact same specificity tuple — it does not override a genuine specificity difference. And ID selectors are not automatically promoted to inline-style status; inline `style` attributes form their own separate, higher tier above IDs entirely.

Question 71 · B-tree properties · hard

A B-tree uses minimum degree t = 4, so every non-root node must hold between t-1 = 3 and 2t-1 = 7 keys, and a node with k keys always has exactly k+1 children. While inserting a new key, the algorithm reaches a full non-root node holding all 7 keys, and per standard B-tree insertion this full node must be split before the insertion proceeds. How many keys does each resulting node hold, and how many keys get promoted to the parent?

  1. Splitting yields two nodes holding 3 keys each, with exactly 1 key — the median of the original 7 — promoted up into the parent node.
  2. The full node splits unevenly into 4 keys and 3 keys, since an odd key count of 7 cannot divide equally, and no key is sent up to the parent.
  3. Each resulting node keeps 3 keys, but the median key is copied into both children rather than sent up, since B-trees replicate keys across levels.
  4. No split happens yet — the node's capacity is temporarily extended to 8 keys, deferring the actual split until the following insertion.

Answer: A. Splitting yields two nodes holding 3 keys each, with exactly 1 key — the median of the original 7 — promoted up into the parent node.

ExplanationWith minimum degree t = 4, a full node holds 2t-1 = 7 keys. The standard B-tree split takes the node's keys in sorted order and identifies the median — the t-th key, the 4th of 7 — to promote into the parent. The remaining six keys divide evenly on either side of that median: the first 3 stay in the original node and the last 3 move into a newly created sibling, so each resulting node ends up with t-1 = 3 keys. If the node was internal, its k+1 = 8 child pointers also split 4 and 4 between the two nodes, keeping every node within the required 3-to-7 key range. A B-tree never leaves a full node unsplit: CLRS-style insertion proactively splits any full node it passes through on the way down, rather than waiting for an 8-key overflow or postponing the split to a later insertion. It also never duplicates a key across the parent and both children — copying keys into every level is a feature of B+ trees, whose internal nodes index copies of leaf keys for fast range scans, not of the plain B-tree described here.

Question 72 · CSS specificity · hard

Four CSS rules below all target the same paragraph element on a page, and none of them use !important: '''css #main .card p { color: red; } .container .card p { color: blue; } div.container p { color: green; } p { color: black; } ''' CSS specificity is calculated as a triple (ID count, class/attribute/pseudo-class count, element/pseudo-element count) and the triples are compared left to right, like place-value digits — a single ID outweighs any number of classes, and a class outweighs any number of elements. Which rule actually wins, and what color does the paragraph render?

  1. '.container .card p' wins with specificity (0,2,1) — blue text, because it has more classes than any other rule and classes are the most specific selector type in CSS
  2. '#main .card p' wins with specificity (1,1,1) — red text, because it contains an ID selector, and any rule with a nonzero ID count beats a rule with more classes but no ID
  3. 'div.container p' wins with specificity (0,1,2) — green text, because it appears third in the stylesheet and CSS always uses source order as the primary tiebreaker whenever multiple rules match the same element
  4. All four rules tie because each selector matches exactly one paragraph, so specificity comparison doesn't apply here, and the browser simply applies the last rule written, 'p { color: black }', giving black text

Answer: B. '#main .card p' wins with specificity (1,1,1) — red text, because it contains an ID selector, and any rule with a nonzero ID count beats a rule with more classes but no ID

ExplanationSpecificity is the triple (IDs, classes, elements), compared left to right like place-value digits: a rule with a higher ID count always wins outright, no matter how many classes or elements the competing rule has. Computing each rule here: '#main .card p' has one ID (#main), one class (.card), one element (p) -> (1,1,1). '.container .card p' has zero IDs, two classes (.container, .card), one element (p) -> (0,2,1). 'div.container p' has zero IDs, one class (.container), two elements (div, p) -> (0,1,2). 'p' has zero IDs, zero classes, one element -> (0,0,1). Comparing left to right, only '#main .card p' has a nonzero value in the ID column, so it wins immediately regardless of how the class and element columns compare -- the paragraph renders red. Option A is wrong because it compares the class column before checking the ID column; two classes never beat one ID. Option C is wrong because source order (the cascade) only breaks ties between rules with genuinely equal specificity -- it never overrides a real specificity difference. Option D is wrong because specificity comparison applies whenever multiple rules match an element, whether that's one paragraph or a thousand; 'last rule wins' is only the correct shortcut when every competing rule has identical specificity (and no !important).

Question 73 · bloom filter · hard

In a coding interview, you are asked to analyze bloom filter: Bloom filter: m=1000 bits, k=3 hash functions, n=100 items. False positive rate: (1-e^(-kn/m))^k = (1-e^(-300/1000))^3 = (0.259)^3 = 1.7%. Check membership in O(k)=O(3) time. No false negatives ever. Cannot delete items (use counting bloom filter). What is the correct false positive rate and lookup time complexity for this bloom filter?

  1. Approximately 26% false positives with O(n) lookup time, since each membership query must scan all n=100 inserted items to be certain
  2. False positives occur at about 1.7%, but false negatives happen at the same rate because hash collisions can overwrite bits set by earlier items
  3. This bloom filter yields roughly 1.7% false positives, O(k) = O(3) lookup time per query, and guarantees zero false negatives
  4. The false positive rate would drop to near 0% if items could be removed by clearing their corresponding bits directly in this bloom filter

Answer: C. This bloom filter yields roughly 1.7% false positives, O(k) = O(3) lookup time per query, and guarantees zero false negatives

ExplanationWith m=1000 bits, k=3 hash functions, and n=100 inserted items, the false positive probability is (1-e^(-kn/m))^k = (1-e^(-300/1000))^3 = (1-e^-0.3)^3 ≈ (1-0.741)^3 ≈ (0.259)^3 ≈ 0.0174, or about 1.7%. Checking membership only requires testing the k=3 bit positions produced by the hash functions, giving O(k) = O(3) lookup time no matter how many items were inserted. Because inserting an item only ever sets bits to 1 and a query only ever reads them, an item that was actually inserted will always have all its bits set, so the filter can never report "absent" for it — false negatives are structurally impossible. It can, however, report "possibly present" for an item that was never inserted if other items happened to set the same bits, which is exactly the false positive being calculated here. Claiming equal false-positive and false-negative rates, or O(n) lookup time, misdescribes how the bit array is built and queried. Clearing bits to delete an item is also unsafe in a standard bloom filter, since those same bits are usually shared with other inserted items; safely removing an item requires a counting bloom filter instead.

Question 74 · CSS specificity · hard

A CBSE student tests CSS specificity with this stylesheet and this HTML, in the exact order shown: ```css .card .title { color: green; } .header .title { color: purple; } ``` ```html <div class="card header"> <h2 class="title">Hello</h2> </div> ``` Both selectors match the `<h2>` element, and neither rule uses `!important`. What color does the browser render the heading's text?

  1. Purple wins: both selectors have identical specificity (0 IDs, 2 classes, 0 elements), so the tie is broken by source order — the rule declared later in the stylesheet takes precedence.
  2. Green wins: specificity ties are broken by keeping whichever rule appears first in the stylesheet, so the earlier of the two equally-specific rules overrides the later one.
  3. Because .card sorts alphabetically before .header, the tie between two equally-specific rules is resolved in favor of .card .title, making the heading green.
  4. Even though both rules look tied on paper, .header .title actually scores higher because matching two classes already present together on one element earns extra specificity beyond the standard count, turning the heading purple.

Answer: A. Purple wins: both selectors have identical specificity (0 IDs, 2 classes, 0 elements), so the tie is broken by source order — the rule declared later in the stylesheet takes precedence.

ExplanationBoth `.card .title` and `.header .title` are descendant selectors built from two class selectors each, so each has a specificity of 0 IDs, 2 classes, and 0 element selectors — an exact tie. When two matching rules in the same cascade layer have identical specificity and neither carries `!important`, CSS does not fall back to alphabetical order of the selector text, and it does not fall back to which class name was written first in the HTML `class` attribute either; it falls back to source order, keeping whichever rule was declared later in the stylesheet. Since `.header .title { color: purple; }` appears after `.card .title { color: green; }`, the browser applies purple to the heading. The claim that the earlier-written rule wins invents a tie-breaking rule that is the exact opposite of how CSS cascade actually works, the alphabetical-ordering claim invents a rule that doesn't exist at all, and the claim that two classes matching on a single element earns "extra" specificity beyond the standard 0-2-0 count is also false — specificity only counts selector components (IDs, classes/attributes/pseudo-classes, and elements/pseudo-elements), never how the underlying HTML groups those classes together.

Question 75 · GraphQL resolver chain · hard

A GraphQL server has an `authors` table and a separate `posts` table (posts store an authorId column). This query is sent: `query { authors { name posts { title } } }`. There are exactly 3 authors in the database. The resolver `Query.authors` makes ONE database call that fetches all 3 authors in a single result set. The resolver `Author.posts` is written naively (no batching): the GraphQL engine calls it once for EACH author object in the list returned by `Query.authors`, and every call to `Author.posts` triggers its own separate database call to fetch that one author's posts. Counting only database calls (not resolver function calls), how many total database calls does the server make to resolve this entire query, and why is this pattern named the "N+1 problem"?

  1. 4 total database calls: 1 call fetches all 3 authors, then 3 more calls each fetch one author's posts (one per author) — it is called N+1 because N per-item calls (here N=3) are added on top of the 1 initial list call
  2. 3 total database calls: GraphQL automatically batches every resolver invocation for the same field into a single database call before execution, so the list size never adds extra calls regardless of how naive the resolver code is
  3. 6 total database calls: each Author.posts call must first re-query the authors table to re-verify that author's id before it can look up posts, so every author accounts for 2 calls instead of 1
  4. 1 total database call: the GraphQL execution engine compiles nested selection sets like `authors { posts }` into a single SQL join at the API layer automatically, so no additional round trips to the database ever occur

Answer: A. 4 total database calls: 1 call fetches all 3 authors, then 3 more calls each fetch one author's posts (one per author) — it is called N+1 because N per-item calls (here N=3) are added on top of the 1 initial list call

ExplanationTrace it call by call. Query.authors runs once and returns all 3 author objects from a single database call — that's call #1. Then, because Author.posts is naive (no DataLoader/batching), the GraphQL engine invokes it separately for each of the 3 author objects in the result list, and each invocation issues its own database call to fetch just that author's posts — that's 3 more calls (call #2, #3, #4). Total = 1 (the list query) + 3 (one per-item query) = 4 database calls. This is called the "N+1 problem" because the pattern is always: 1 query to fetch a list of N items, plus N additional queries (one per item) to fetch each item's related data — here N=3 authors, so 1+3=4. The fix is batching (e.g., DataLoader), which collects all 3 pending Author.posts lookups within a single tick and issues one combined "fetch posts WHERE authorId IN (1,2,3)" call instead of 3 separate ones, cutting the total from 4 down to 2 calls. The other options are wrong: GraphQL does not auto-batch resolvers, does not re-verify author ids, and does not auto-compile nested selections into SQL joins — resolver execution strategy is entirely up to what the developer writes.

Question 76 · B-tree properties · hard

A B-tree has minimum degree t = 3, so every non-root node can hold at most 2t − 1 = 5 keys and every full node splits before the algorithm ever descends into it during insertion. While inserting key 25, the search path reaches a full leaf holding keys [10, 20, 30, 40, 50]. Following the standard rule — split a full node's keys around their median before continuing the descent — which key is promoted to the parent, and where does 25 end up after the split?

  1. The middle key, 30, moves up to the parent, and 25 is inserted into the left child, making it [10, 20, 25].
  2. The newly inserted key, 25, moves up to the parent, splitting the node into [10, 20] and [30, 40, 50].
  3. The fourth key, 40, moves up to the parent, splitting the node into [10, 20, 30] and [50], with 25 landing in the left child.
  4. All five original keys stay in one node and 25 is added as a sixth key, since B-trees only split once a node exceeds 2t keys, not when it reaches 2t − 1.

Answer: A. The middle key, 30, moves up to the parent, and 25 is inserted into the left child, making it [10, 20, 25].

ExplanationA B-tree with minimum degree t = 3 allows at most 2t − 1 = 5 keys per node, so a full node splits before a new key ever gets inserted into it. Sorting the leaf's keys [10, 20, 30, 40, 50], the median is the 3rd key, 30, which moves up into the parent. The remaining keys divide evenly around it: [10, 20] stays as the left child and [40, 50] becomes the right child, each holding t − 1 = 2 keys as required for a non-root node. Only after this split does the algorithm decide where 25 belongs — since 25 is less than the promoted key 30, it descends into the left child, which becomes [10, 20, 25], still within the allowed 2-to-5-key range. Claiming that the newly inserted key itself gets promoted confuses B-tree splitting with a different insertion strategy; the promoted key is always the median of the existing full node, never the incoming key. Picking 40 as the promoted key miscounts the median position, treating the 4th key of five as the middle instead of the 3rd. And letting the node grow to six keys before splitting ignores the defining B-tree invariant that a node is never allowed to exceed 2t − 1 keys even momentarily during a top-down insertion — that is exactly why the split happens on the way down, before 25 is placed.

Question 77 · LRU cache implementation · hard

A Least Recently Used (LRU) cache is implemented with capacity = 2, using a hash map plus a doubly linked list so that both get() and put() run in O(1) time. The list always keeps entries ordered from least-recently-used (LRU) to most-recently-used (MRU). Trace this sequence of operations in order: ``` put(1, "A") # cache: {1} order: [1] put(2, "B") # cache: {1,2} order: [1,2] get(1) # hit "A" order: [2,1] put(3, "C") # full -> evict LRU=2 order: [1,3] get(2) # miss (already gone) order: [1,3] (miss never changes order) get(3) # hit "C" order: [1,3] (3 already MRU) put(4, "D") # full -> evict LRU=1 order: [3,4] ``` If one more operation, put(5, "E"), is now performed immediately after put(4, "D"), which key gets evicted from the cache?

  1. The key inserted first, key 1, is evicted next — LRU caches remove entries strictly in original insertion order regardless of later hits.
  2. Key 4, the entry just added by the last put(), is evicted next, since an LRU cache always discards its newest member to make room for another new one.
  3. Key 3 is evicted next, because get(3) happened before key 4 was inserted, leaving key 3 as the least-recently-used of the two keys still in the cache.
  4. Because get(2) executed after key 2 had already been evicted, it counts as recent activity on key 2, which is therefore evicted next after being kept alive by that miss.

Answer: C. Key 3 is evicted next, because get(3) happened before key 4 was inserted, leaving key 3 as the least-recently-used of the two keys still in the cache.

ExplanationWalking through the trace step by step: put(1,"A") and put(2,"B") fill the capacity-2 cache with order [1,2] (1 is LRU, 2 is MRU). get(1) hits and moves 1 to the MRU end, giving order [2,1]. put(3,"C") finds the cache full, so it evicts the current LRU key, which is 2, and inserts 3 at the MRU end, giving order [1,3]. get(2) misses since key 2 was just evicted — a miss never touches the linked list, so order stays [1,3]. get(3) hits, but 3 is already at the MRU end, so the order is unchanged: [1,3]. put(4,"D") again finds the cache full, evicts the current LRU key, which is 1, and inserts 4 at the MRU end, giving order [3,4]. At this point key 3 is the least-recently-used of the two remaining keys, so a following put(5,"E") evicts key 3. The claim about key 1 ignores that it was already evicted two steps earlier and mistakenly treats LRU as plain insertion-order (FIFO) eviction. The claim about key 4 reverses the eviction rule, treating the cache as if it discards the most-recently-used entry rather than the least-recently-used one. The claim about key 2 misunderstands what a cache miss does — get(2) returned nothing because key 2 was no longer stored, and a miss cannot refresh or "keep alive" a key that isn't present.

Question 78 · Stack-Based Bracket Matching · hard

What does this stack-based algorithm output? 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("({[}])")) What is printed?

  1. True then False — "({[]})" is properly nested, but "({[}])" has } closing [ which is mismatched
  2. True then True — both strings have equal numbers of opening and closing brackets
  3. False then False — neither string has matching brackets at every position
  4. True then Error — the second string causes an IndexError when stack is empty

Answer: A. True then False — "({[]})" is properly nested, but "({[}])" has } closing [ which is mismatched

ExplanationFirst string "({[]})": '(' → stack=['(']. '{' → stack=['(','{']. '[' → stack=['(','{','[']. ']' → pairs[']']='[', stack[-1]='[' matches, pop → stack=['(','{']. '}' → pairs['}']='{', stack[-1]='{' matches, pop → stack=['(']. ')' → pairs[')']='(', stack[-1]='(' matches, pop → stack=[]. Return len([])==0 → True. Second string "({[}])": '(' → stack=['(']. '{' → stack=['(','{']. '[' → stack=['(','{','[']. '}' → pairs['}']='{' but stack[-1]='[' ≠ '{'. Return False immediately. The mismatch occurs because '}' tries to close '{' but '[' is actually on top of the stack, so '}' ends up closing over '[' instead.

Question 79 · Generators and Fibonacci Sequence · hard

Consider this Python generator function: ```python def sequence_gen(n): a, b = 1, 1 count = 0 while count < n: yield b a, b = b, a + b count += 1 result = list(sequence_gen(6)) print(result[-2:], sum(result)) ``` What are the values printed for `result[-2:]` and for `sum(result)`?

  1. [8, 13] and 32, since result = [1, 2, 3, 5, 8, 13] and adding all six terms gives 32
  2. [5, 8] and 20, because the generator is assumed to yield `a` before the swap, like a typical Fibonacci generator
  3. [13, 21] and 53, because the while loop is assumed to run one extra time once count reaches n
  4. [8, 13] and 21, because only the two sliced values 8 and 13 are added together

Answer: A. [8, 13] and 32, since result = [1, 2, 3, 5, 8, 13] and adding all six terms gives 32

ExplanationTrace the generator step by step: with a, b = 1, 1, each pass through the loop first yields b, then updates a, b = b, a + b, then increments count. Pass 1 yields 1 (a,b become 1,2). Pass 2 yields 2 (a,b become 2,3). Pass 3 yields 3 (a,b become 3,5). Pass 4 yields 5 (a,b become 5,8). Pass 5 yields 8 (a,b become 8,13). Pass 6 yields 13 (a,b become 13,21), and count now equals 6 so the loop stops. So result = [1, 2, 3, 5, 8, 13]. This is a Fibonacci sequence, but because b (not a) is yielded, the classic leading 1,1 pair collapses into a single first term, shifting which number appears at each position. The last two elements, result[-2:], are [8, 13], and sum(result) totals every element in the full list, not just the sliced pair: 1 + 2 + 3 + 5 + 8 + 13 = 32.

Question 80 · Boyer-Moore Majority Vote · hard

What does this function return for the input [3, 1, 2, 3, 1, 2, 3]? def find_majority(arr): count = 0 candidate = None for num in arr: if count == 0: candidate = num count += 1 if num == candidate else -1 return candidate. What value is returned?

  1. 3 — Boyer-Moore voting: candidate starts as 3 (count 1), count drops to 0 at index 3, resets to 3, count reaches 1 at end; 3 appears 3 times out of 7 which is the plurality
  2. 1 — the algorithm returns the first element that appears more than once
  3. 2 — the algorithm finds the median value of the array
  4. None — no element has a strict majority (more than half), so the algorithm fails

Answer: A. 3 — Boyer-Moore voting: candidate starts as 3 (count 1), count drops to 0 at index 3, resets to 3, count reaches 1 at end; 3 appears 3 times out of 7 which is the plurality

ExplanationTrace: i=0: count=0, candidate=3, count=1. i=1: 1≠3, count=0. i=2: count=0, candidate=2, count=1. i=3: 3≠2, count=0. i=4: count=0, candidate=1, count=1. i=5: 2≠1, count=0. i=6: count=0, candidate=3, count=1. Returns 3. Note: 3 appears 3/7 times (not strict majority), but the algorithm returns the candidate — a second pass would be needed to verify. The question asks what it returns, which is 3.
← Set 3Set 5 →