Question 201 · Progressive Enhancement: Graceful Degradation · hard
A frontend developer at AI Computer Institute builds a collapsible "Read More" section, intending it to enhance the page for visitors with JavaScript while still degrading gracefully for those without it:
```html
<style>
.hidden { display: none; }
</style>
<button onclick="document.getElementById('details').classList.toggle('hidden')">
Read More
</button>
<div id="details" class="hidden">
AICI now offers a new Applied AI elective for Grade 11 students.
</div>
<noscript>
<style>
.hidden { display: block; }
</style>
</noscript>
```
If a visitor opens this exact page in a browser with JavaScript completely disabled, what happens to the "Applied AI elective" text, and why?
The text stays permanently hidden, because a `<noscript>` element's contents are never rendered by any browser under any condition.
The text becomes permanently visible without any click, because `<noscript>` markup is parsed as real HTML only when scripting is off, and its later `.hidden { display: block; }` rule beats the earlier rule of equal specificity.
The text appears only after the button is clicked, because disabling JavaScript merely stops the click handler from firing but leaves both CSS rules and their original cascade order unchanged.
The text flickers between hidden and visible on every reload, because the browser cannot resolve two rules that use the exact same class selector.
Answer: B. The text becomes permanently visible without any click, because `<noscript>` markup is parsed as real HTML only when scripting is off, and its later `.hidden { display: block; }` rule beats the earlier rule of equal specificity.
ExplanationWhether a browser treats the markup inside `<noscript>` as functioning HTML depends entirely on its scripting state: when scripting is enabled, that markup stays inert and unparsed, but when scripting is disabled, the browser parses it exactly like normal page content. Disabling JavaScript therefore activates the nested `.hidden { display: block; }` rule as a genuine, working stylesheet rule. That rule has identical specificity to the earlier `.hidden { display: none; }` rule, since both use a single class selector, so the CSS cascade's tie-breaker applies: whichever matching rule appears later in the document wins, regardless of what the JavaScript toggle was meant to do. This is precisely why the pattern is used for graceful degradation — it guarantees that information meant to be revealed by an interactive toggle is never permanently lost to visitors on older devices, low-end browsers, or networks where JavaScript may fail to load or run.
Question 202 · BFS vs DFS: Shortest Path · medium
You are exploring a maze represented as a graph, and you specifically need the SHORTEST path in terms of number of edges between the entrance and exit (an unweighted graph). Should you use BFS or DFS, and why?
DFS, because it uses less memory in general
BFS — Breadth-First Search explores the graph level by level (all nodes at distance 1 from the start, then all at distance 2, etc.), so the FIRST time it reaches the exit node is guaranteed to be via a shortest path in an unweighted graph; DFS explores one path as deep as possible first and offers no such guarantee
DFS, because it always finds a path faster in every case
Neither works on graphs with cycles
Answer: B. BFS — Breadth-First Search explores the graph level by level (all nodes at distance 1 from the start, then all at distance 2, etc.), so the FIRST time it reaches the exit node is guaranteed to be via a shortest path in an unweighted graph; DFS explores one path as deep as possible first and offers no such guarantee
ExplanationBFS explores nodes in order of increasing distance from the start — it fully processes all nodes 1 edge away before touching any node 2 edges away, and so on. This level-by-level expansion means the very first time BFS discovers the target node, it has done so via the minimum possible number of edges — a mathematical guarantee for unweighted graphs. DFS, by contrast, commits to one path and follows it as deep as it goes before backtracking; it might stumble onto a long, winding path to the exit long before it would find the actual shortest one, with no built-in guarantee of optimality. This is exactly why BFS, not DFS, is the standard choice for unweighted shortest-path problems, while DFS is preferred for tasks like exhaustively exploring all possibilities or detecting cycles.
Question 203 · Dijkstra's Algorithm · hard
A weighted graph has edges: A-B (weight 4), A-C (weight 1), C-B (weight 2), B-D (weight 5), C-D (weight 8). Running Dijkstra's algorithm from A, what is the shortest distance from A to D, and via which path?
Distance 9, via A-B-D directly (4+5=9)
Distance 8, via A-C-D directly (1+8=9, so this is wrong on the arithmetic, making it not the true shortest)
Distance 8, via A-C-B-D (A to C is 1, C to B is 2, B to D is 5, totaling 1+2+5=8) — this beats both the direct A-B-D route (4+5=9) and the direct A-C-D route (1+8=9)
Distance 9, via A-C-D (1+8=9)
Answer: C. Distance 8, via A-C-B-D (A to C is 1, C to B is 2, B to D is 5, totaling 1+2+5=8) — this beats both the direct A-B-D route (4+5=9) and the direct A-C-D route (1+8=9)
ExplanationDijkstra's algorithm finds the minimum-total-weight path by greedily expanding the closest unvisited node at each step, always keeping the best-known distance to every node. Checking every route from A to D by hand: A-B-D costs 4+5=9. A-C-D costs 1+8=9. A-C-B-D costs 1+2+5=8. The three-edge path through both C and B, despite using more edges, has the LOWEST total weight at 8 — beating both two-edge alternatives, which is exactly the kind of non-obvious result Dijkstra's algorithm is designed to find correctly (fewer edges does not necessarily mean lower total weight in a weighted graph). Dijkstra's algorithm would correctly settle on distance 8 by continuously relaxing edges: it finds C first (distance 1), then from C it discovers B at distance 1+2=3 (better than the direct A-B edge of 4), and finally from B it reaches D at distance 3+5=8 (better than C-D's direct 1+8=9).
Question 204 · HTTP Method Idempotency · medium
A REST API endpoint POST /orders is called twice in a row with the exact same request body, due to a flaky network causing a client-side retry. A GET /orders/42 is also called twice. Which of these two requests is guaranteed to be IDEMPOTENT (calling it multiple times has the same effect as calling it once), by HTTP convention, and which typically is not?
Both POST and GET are idempotent by definition — 'idempotent' just means 'read-only'
GET is idempotent (fetching the same resource twice doesn't change anything); POST is typically NOT idempotent — calling POST /orders twice conventionally creates TWO separate orders, since each POST is treated as 'create a new resource', which is exactly why the flaky-retry scenario above is a real, common bug (duplicate orders)
POST is idempotent because it always returns the same response body; GET is not because it depends on server state
Neither is idempotent since both involve network requests
Answer: B. GET is idempotent (fetching the same resource twice doesn't change anything); POST is typically NOT idempotent — calling POST /orders twice conventionally creates TWO separate orders, since each POST is treated as 'create a new resource', which is exactly why the flaky-retry scenario above is a real, common bug (duplicate orders)
ExplanationIdempotency means: performing the same operation multiple times produces the same end STATE as performing it once. GET is defined by the HTTP spec to be safe and idempotent — it only reads data, so calling it once or a hundred times leaves the server's state identical. POST, however, is conventionally used to CREATE a new resource each time it's called — POST /orders typically means 'create a new order', so calling it twice with an identical body still results in two distinct order records (with different IDs), not one. This exact behavior is why network retries on POST requests are dangerous without extra safeguards (like idempotency keys), while retrying a GET request is always safe. PUT, by contrast, IS conventionally idempotent, since 'replace this resource with this exact state' has the same end result whether done once or five times.
Question 205 · JWT Structure · medium
A JSON Web Token (JWT) looks like this: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjQyfQ.4f9x... What are the three dot-separated parts, in order?
Username, password, and a checksum
Header (algorithm/type info), Payload (the actual claims/data, like userId), and Signature (a cryptographic value verifying the token wasn't tampered with) — the first two parts are only BASE64-ENCODED, not encrypted, so anyone can decode and read them, but only the signature protects against forgery
Encryption key, encrypted data, and initialization vector
Server hostname, session ID, and expiry timestamp only
Answer: B. Header (algorithm/type info), Payload (the actual claims/data, like userId), and Signature (a cryptographic value verifying the token wasn't tampered with) — the first two parts are only BASE64-ENCODED, not encrypted, so anyone can decode and read them, but only the signature protects against forgery
ExplanationA JWT's three dot-separated segments are, in order: (1) the HEADER, base64-encoded JSON describing the signing algorithm and token type (e.g., {'alg':'HS256'}); (2) the PAYLOAD, base64-encoded JSON containing the actual claims — data like userId, expiration time, or roles; and (3) the SIGNATURE, a cryptographic hash computed over the first two parts using a secret key known only to the server, which lets the server verify the token hasn't been tampered with since it was issued. A critical, commonly-missed detail: base64 encoding is NOT encryption — it is trivially reversible by anyone, so the header and payload can be decoded and read by anybody who has the token (never put secrets like a password directly in a JWT payload). Only the signature, which requires the server's secret key to forge correctly, provides real security — it's what makes a tampered payload detectable, not what hides the payload's contents.
Question 206 · CSS Box Model · medium
A div has box-sizing: content-box (the CSS default) with width: 200px, padding: 20px, and border: 5px solid. What is the TOTAL rendered width of this element, including padding and border?
200px — padding and border are drawn OUTSIDE the specified width but don't count as part of it visually
250px — total width = content width (200) + padding on both sides (20+20=40) + border on both sides (5+5=10), so 200+40+10=250, because content-box means the 'width' property refers ONLY to the content area, with padding and border added on top
220px — only one side's padding and border are added
200px exactly, because box-sizing: content-box forces padding and border to be included within the stated width
Answer: B. 250px — total width = content width (200) + padding on both sides (20+20=40) + border on both sides (5+5=10), so 200+40+10=250, because content-box means the 'width' property refers ONLY to the content area, with padding and border added on top
ExplanationThe default box-sizing value, content-box, defines the 'width' CSS property as applying ONLY to the content area — padding and border are then added ON TOP of that, expanding the element's actual rendered footprint. Computing it: content width = 200px. Padding applies to BOTH left and right sides: 20px + 20px = 40px. Border also applies to both sides: 5px + 5px = 10px. Total rendered width = 200 + 40 + 10 = 250px. This is a frequent source of unexpected layout bugs, which is exactly why box-sizing: border-box (where the specified width INCLUDES padding and border, making the content area shrink to fit) is so commonly applied as a CSS reset in real projects — it makes the 'width' property mean what most developers intuitively expect.
Question 207 · Event Delegation · medium
A page has a list with 1000 <li> items, and you want ONE click handler that responds when ANY item is clicked, without attaching 1000 separate event listeners (which wastes memory and is slow to set up). What technique accomplishes this?
You must attach a listener to each <li> individually — there is no alternative in JavaScript
Event delegation — attach a SINGLE click listener to the parent <ul> (or an ancestor), and rely on event BUBBLING (a click on any child li 'bubbles up' through the DOM tree to the parent); inside the handler, check event.target to determine which specific li was actually clicked
Use setInterval to continuously poll all 1000 items for clicks every few milliseconds
Reduce the list to fewer than 100 items, since JavaScript cannot handle click events on more elements than that
Answer: B. Event delegation — attach a SINGLE click listener to the parent <ul> (or an ancestor), and rely on event BUBBLING (a click on any child li 'bubbles up' through the DOM tree to the parent); inside the handler, check event.target to determine which specific li was actually clicked
ExplanationDOM events don't just fire on the exact element clicked — by default they BUBBLE upward through every ancestor element, all the way to the document root. Event delegation exploits this: instead of attaching 1000 listeners (one per li, wasteful in both memory and setup time, and broken for any li added LATER dynamically), you attach exactly ONE listener to a shared ancestor (like the ul). When any li is clicked, the event bubbles up to the ul's listener, which fires once; inside that handler, event.target tells you exactly which specific li was the original click target, so you can respond appropriately. This pattern is both more efficient AND automatically handles dynamically-added list items, since the single listener on the parent doesn't care how many children exist or when they were added.
Question 208 · Git Branching Workflow · easy
You run: git checkout -b feature-login; then make some commits; then git checkout main; git merge feature-login. What does this sequence of commands accomplish?
It permanently deletes the main branch and replaces it with feature-login
It creates a new branch called feature-login (starting from wherever you were), switches to it so your new commits happen there in isolation from main, then switches back to main and merges feature-login's commits INTO main — the standard workflow for developing a feature without disturbing the main branch until it's ready
It copies every file in the repository into a new folder called feature-login
It undoes all commits made since the repository was created
Answer: B. It creates a new branch called feature-login (starting from wherever you were), switches to it so your new commits happen there in isolation from main, then switches back to main and merges feature-login's commits INTO main — the standard workflow for developing a feature without disturbing the main branch until it's ready
Explanationgit checkout -b feature-login does two things at once: creates a new branch named feature-login (a separate line of commit history starting from your current position), and switches your working directory to it. Any commits you make next belong to feature-login specifically, leaving main completely untouched — this isolation is the entire point of feature branches, letting you experiment or build a feature without risking main's stability. git checkout main switches back to the main branch. git merge feature-login then integrates feature-login's new commits into main, combining the two histories. This branch-work-merge cycle is the fundamental Git workflow behind nearly all real-world collaborative software development, letting multiple people work on different features in parallel without stepping on each other's changes until merge time.
Question 209 · Trie Data Structure · hard
A trie (prefix tree) is used to store the words 'cat', 'car', 'card', and 'dog'. How many nodes does the path from the root to the final 'd' in 'card' pass through (counting the root as one node), and why is a trie efficient for prefix-based lookups like autocomplete?
4 nodes (root, c, a, r, d is 5 characters after root so 5 total, but sharing matters) — regardless of the exact count, the KEY property is that 'cat', 'car', and 'card' all share the same c-a-r prefix path in the tree, so that shared prefix is stored ONCE rather than duplicated across all three words, and looking up 'all words starting with car' means finding the 'car' node and exploring everything beneath it — no need to scan the full word list
A trie stores every word as a completely separate, unconnected path with zero sharing between words
A trie is a type of hash table optimized for numeric keys only
A trie can only store words of exactly the same length
Answer: A. 4 nodes (root, c, a, r, d is 5 characters after root so 5 total, but sharing matters) — regardless of the exact count, the KEY property is that 'cat', 'car', and 'card' all share the same c-a-r prefix path in the tree, so that shared prefix is stored ONCE rather than duplicated across all three words, and looking up 'all words starting with car' means finding the 'car' node and exploring everything beneath it — no need to scan the full word list
ExplanationA trie's core structural idea is that words sharing a common PREFIX also share the same PATH through the tree from the root. For 'cat', 'car', and 'card': all three share the letters c-a-r, so the tree has a single c->a->r path used by all three words, only branching apart afterward ('t' for cat, nothing extra marks the end of 'car' itself, 'd' extends to 'card'). This shared-prefix structure is exactly why tries are the standard data structure behind autocomplete and spell-checkers: to find every word starting with 'car', you simply walk down to the 'car' node (following just 3 edges) and then explore everything reachable beneath it — you never need to scan through unrelated words like 'dog' at all, giving lookup time proportional to the PREFIX length, not the total number of stored words.
Question 210 · Web Accessibility (alt text) · easy
A web-accessibility audit flags an <img src='chart.png'> tag for missing an alt attribute. Why does this matter, beyond just following a rule?
It only matters for search engine ranking, with no impact on actual users
Screen readers (used by blind or low-vision users) cannot 'see' an image — they rely entirely on the alt attribute's text to describe what the image conveys to a sighted user; without it, a screen reader either announces nothing meaningful or just reads the filename ('chart dot png'), leaving that user with no idea what information the image was meant to communicate
alt text is purely decorative and has no functional purpose in any browser
Missing alt attributes cause the image to fail to load entirely
Answer: B. Screen readers (used by blind or low-vision users) cannot 'see' an image — they rely entirely on the alt attribute's text to describe what the image conveys to a sighted user; without it, a screen reader either announces nothing meaningful or just reads the filename ('chart dot png'), leaving that user with no idea what information the image was meant to communicate
ExplanationThe alt attribute exists specifically to provide a text alternative to an image's visual content, and its single most important use case is screen-reader software used by blind or low-vision users, who cannot perceive the image visually at all. A well-written alt text (e.g., alt='Bar chart showing 40% revenue growth in Q3 2026') lets a screen reader convey the SAME information a sighted user gets from glancing at the chart. Without it, a screen reader typically falls back to reading the raw filename, which conveys essentially nothing useful ('chart dot p n g'). This is a genuine accessibility gap, not a cosmetic rule — it directly determines whether a meaningful fraction of real users can actually understand your page's content, which is exactly why alt-text auditing is a standard, legally-relevant part of web accessibility compliance (WCAG) in many countries.
Question 211 · SQL INNER JOIN · medium
Two SQL tables: Students(id, name) has rows (1,'Aisha'), (2,'Rohan'), (3,'Meera'); Grades(student_id, subject, score) has rows (1,'Math',90), (1,'Science',85), (2,'Math',70) — notice student 3 (Meera) has NO row in Grades. Running SELECT Students.name, Grades.score FROM Students INNER JOIN Grades ON Students.id = Grades.student_id — how many rows does this return, and is Meera included?
4 rows, including Meera with a NULL score
3 rows: Aisha/90, Aisha/85, Rohan/70 — Meera is EXCLUDED entirely, because INNER JOIN only returns rows where the join condition finds a MATCH in both tables; since Meera has no matching row in Grades, she produces nothing in the result
5 rows, one for every possible combination of the two tables
1 row, since INNER JOIN only returns the single best match
Answer: B. 3 rows: Aisha/90, Aisha/85, Rohan/70 — Meera is EXCLUDED entirely, because INNER JOIN only returns rows where the join condition finds a MATCH in both tables; since Meera has no matching row in Grades, she produces nothing in the result
ExplanationINNER JOIN returns only the rows where the join condition (Students.id = Grades.student_id) successfully matches a row in BOTH tables — any row from either table with no corresponding match is silently dropped from the result entirely. Aisha (id=1) matches two Grades rows (Math and Science), producing two output rows. Rohan (id=2) matches one Grades row (Math), producing one output row. Meera (id=3) has ZERO matching rows in Grades, so she contributes nothing — not even a row with a NULL score. Total: 2+1+0 = 3 rows, and Meera is completely absent from the result. This is the key distinction from a LEFT JOIN, which would still include Meera exactly once, with score shown as NULL, specifically because LEFT JOIN preserves every row from the left table (Students) regardless of whether a match exists on the right.
Question 212 · Heap vs BST for Priority Access · hard
A binary max-heap stores priorities so the LARGEST value is always retrievable in O(1) time (it's at the root), and insertion/removal are both O(log n). A binary search tree (BST) can also find its maximum value, but that operation is O(h) where h is the tree's height (which can be O(n) if unbalanced). For a task that ONLY ever needs 'repeatedly extract the current maximum', which structure is the better fit, and why?
A BST, because it supports more operations overall, so it's always the safer general-purpose choice
A heap — since the task ONLY needs repeated max-extraction (not arbitrary search, range queries, or in-order traversal), a heap's guaranteed O(1) peek-at-max and O(log n) removal is simpler AND provably efficient, whereas a BST's max-lookup can degrade to O(n) in the worst case (a skewed, unbalanced tree) unless you specifically use a SELF-BALANCING BST
Neither structure can find a maximum value at all
A plain unsorted array, because it requires no extra bookkeeping
Answer: B. A heap — since the task ONLY needs repeated max-extraction (not arbitrary search, range queries, or in-order traversal), a heap's guaranteed O(1) peek-at-max and O(log n) removal is simpler AND provably efficient, whereas a BST's max-lookup can degrade to O(n) in the worst case (a skewed, unbalanced tree) unless you specifically use a SELF-BALANCING BST
ExplanationThe right data structure depends on exactly which operations you actually need. A heap is PURPOSE-BUILT for 'always know the max (or min) quickly, and efficiently remove it, and efficiently add new items' — its structural invariant (every parent >= its children, for a max-heap) guarantees the root is always the maximum, giving true O(1) peek and O(log n) insert/extract, REGARDLESS of what data is inside. A plain (non-self-balancing) BST offers more general capabilities — like arbitrary search, in-order traversal, or range queries — but as a side effect of that generality, an unlucky insertion order can leave it heavily skewed (approaching a straight line), degrading operations like 'find the maximum' from the ideal O(log n) all the way to O(n). For a task that specifically and only needs repeated max-extraction — like a priority queue for task scheduling or Dijkstra's algorithm's frontier — a heap is the simpler, more reliably efficient choice; a self-balancing BST (like a red-black tree) could match a heap's guarantees but at genuinely higher implementation complexity for no added benefit in this narrow use case.
Question 213 · Topological Sort · hard
A build system needs to determine a valid ORDER to compile files, given dependencies like 'file B depends on file A' (meaning A must be compiled before B). This is modeled as a directed graph where an edge A->B means 'A must come before B'. What algorithm produces a valid compile order, and what specific graph property must hold for one to exist?
Topological sort — it produces a linear ordering of nodes such that for every directed edge A->B, A appears before B in the ordering; a valid topological order exists IF AND ONLY IF the graph is a DAG (Directed Acyclic Graph, i.e., contains NO cycles) — a cycle (like A depends on B depends on A) would make a valid build order logically impossible
Binary search, since it can order any list of items in O(log n) time
Dijkstra's algorithm, since it works on any directed graph regardless of structure
Breadth-first search alone, with no additional requirement on the graph's structure
Answer: A. Topological sort — it produces a linear ordering of nodes such that for every directed edge A->B, A appears before B in the ordering; a valid topological order exists IF AND ONLY IF the graph is a DAG (Directed Acyclic Graph, i.e., contains NO cycles) — a cycle (like A depends on B depends on A) would make a valid build order logically impossible
ExplanationTopological sort is specifically defined for Directed Acyclic Graphs (DAGs) and produces exactly what's needed here: a linear sequence of all nodes where every directed edge A->B has A appearing strictly before B. This maps perfectly onto 'A must be compiled before B' dependency constraints. The critical requirement is that the graph must have NO CYCLES — if file A depends on file B, and file B (directly or indirectly) also depends on file A, there is NO valid compile order at all, since satisfying one dependency would require violating the other; this is precisely a 'circular dependency' error that real build tools (like npm, Maven, or Make) detect and report as a build failure. Algorithms like Kahn's algorithm (repeatedly removing nodes with no remaining incoming edges) or a DFS-based approach both compute a valid topological order when one exists, and both can detect the absence of one (a remaining cycle) when it doesn't.
Question 214 · REST vs GraphQL · medium
An API team is deciding between REST and GraphQL for a mobile app. The app's home screen needs a user's name, their last 3 orders (each with just a product name and price, not full order details), and their loyalty points — three DIFFERENT pieces of data that, in a typical REST design, would live at three separate endpoints (/user, /orders, /loyalty). What specific advantage does GraphQL offer here?
GraphQL is always faster than REST for every single request regardless of what data is needed
GraphQL lets the client specify EXACTLY which fields it needs from potentially multiple resources in a SINGLE request/response round-trip — instead of three separate REST calls (each possibly over-fetching full objects, like an entire order history when only 3 items are needed), one GraphQL query can request just {user{name}, orders(limit:3){productName,price}, loyaltyPoints} and get exactly that shape back
GraphQL does not require a network request at all, unlike REST
GraphQL can only be used with SQL databases, never with REST-style backends
Answer: B. GraphQL lets the client specify EXACTLY which fields it needs from potentially multiple resources in a SINGLE request/response round-trip — instead of three separate REST calls (each possibly over-fetching full objects, like an entire order history when only 3 items are needed), one GraphQL query can request just {user{name}, orders(limit:3){productName,price}, loyaltyPoints} and get exactly that shape back
ExplanationREST APIs are typically organized around fixed RESOURCE endpoints, each returning a fixed shape of data — fetching data that spans multiple resources (user info + orders + loyalty points) conventionally means multiple separate HTTP requests, and each individual endpoint might return MORE fields than the client actually needs (over-fetching) or force additional requests to get related data (under-fetching). GraphQL instead exposes a single endpoint where the CLIENT describes precisely the shape of data it wants, across potentially multiple underlying resources, in one query — the server resolves that query and returns exactly the requested fields in one response. For this home-screen scenario, that's the difference between 3 network round-trips (with excess unused data in each) and 1 round-trip carrying only the 5-6 specific fields actually needed — a meaningful real-world performance and simplicity win, especially on mobile networks where each round-trip has real latency cost.
Question 215 · Regular Expressions Basics · medium
A form validates a phone number using the regular expression ^\d{10}$. Which of these inputs would this regex successfully MATCH?
'98765-43210' (with a hyphen) — the regex is flexible about formatting characters
'9876543210' (exactly 10 digits, nothing else) — ^ anchors to the start of the string, \d{10} requires EXACTLY 10 digit characters in a row, and $ anchors to the end, so ANY extra character (a hyphen, a space, an 11th digit, or fewer than 10 digits) fails to match
'+919876543210' (with a country code prefix) — the + and extra digits are simply ignored
'987654321' (only 9 digits) — since \d{10} means 'up to 10 digits'
Answer: B. '9876543210' (exactly 10 digits, nothing else) — ^ anchors to the start of the string, \d{10} requires EXACTLY 10 digit characters in a row, and $ anchors to the end, so ANY extra character (a hyphen, a space, an 11th digit, or fewer than 10 digits) fails to match
ExplanationBreaking down ^\d{10}$: the caret ^ anchors the match to the very START of the string (nothing is allowed before it matches), \d{10} requires EXACTLY 10 digit characters consecutively (not 'up to 10' — {10} is an exact repetition count, unlike {0,10} or {10,} which would allow ranges), and the dollar sign $ anchors to the very END of the string (nothing is allowed after). Together, these three pieces mean the ENTIRE string, start to finish, must consist of precisely 10 digits and nothing else. '9876543210' (10 digits, no extra characters) matches perfectly. '98765-43210' fails because the hyphen is not a digit character, breaking the \d{10} requirement partway through. '+919876543210' fails at the very start, since + immediately violates the ^\d requirement that the string must BEGIN with a digit. '987654321' has only 9 digits, failing the exact-10 requirement enforced by both the {10} count and the $ end-anchor (which would require the 10th digit to exist before the string can end).
Question 216 · Async JavaScript Execution Order · hard
async function loadData() { console.log('A'); const result = await fetch('/api/data'); console.log('B'); return result; } console.log('C'); loadData(); console.log('D'). Ignoring the exact timing of the network request itself, in what order do 'A', 'C', and 'D' get logged (before the fetch resolves)?
A, C, D — the function executes fully before any code after it runs, since JavaScript always finishes one function before starting the next line
C, A, D — loadData() starts running synchronously the moment it's called (so 'A' logs immediately, before await pauses execution at the fetch line), but 'C' was already logged the line before loadData() was even called; once fetch's await pauses the async function, control returns to the caller, allowing 'D' to log BEFORE the fetch resolves and 'B' eventually runs
D, C, A — logs always happen in reverse declaration order in async code
A, D, C — await blocks the entire program, including code after loadData()
Answer: B. C, A, D — loadData() starts running synchronously the moment it's called (so 'A' logs immediately, before await pauses execution at the fetch line), but 'C' was already logged the line before loadData() was even called; once fetch's await pauses the async function, control returns to the caller, allowing 'D' to log BEFORE the fetch resolves and 'B' eventually runs
ExplanationA common misconception is that 'async' means a function runs entirely in the background immediately. In reality: an async function runs SYNCHRONOUSLY from its start, right up until it hits its first await — only AT that await point does it pause and return control back to whatever called it. Tracing this code: line 'console.log(C)' runs first as the program executes top to bottom, printing C. Then loadData() is called; INSIDE it, console.log('A') runs immediately (synchronously) before any pausing happens, printing A. Then execution reaches await fetch(...) — THIS is where it pauses, handing control back to the caller without waiting for the network response. Back in the outer code, the very next line, console.log('D'), then runs, printing D. Only later, once the network request actually completes, does execution resume inside loadData() at the point after await, eventually printing B. Final order: C, A, D, and eventually B — demonstrating that await only pauses the function IT'S inside, never the rest of the program.
Question 217 · localStorage vs sessionStorage · medium
A shopping cart's contents need to persist if the user closes the tab and reopens the site later, but a one-time 'you scrolled 50%' notice should NOT reappear if the user just refreshes the SAME tab a moment later. Which two browser storage mechanisms fit these two needs respectively?
Both should use cookies, since cookies are the only browser storage option
localStorage for the cart (persists indefinitely across tabs and browser restarts, until explicitly cleared) and sessionStorage for the scroll notice (persists only for the lifetime of that specific TAB — cleared automatically when the tab is closed, but survives a same-tab refresh)
sessionStorage for the cart and localStorage for the notice — the reverse of the correct pairing
Neither mechanism can distinguish between a tab refresh and a tab close, so this distinction is impossible in a browser
Answer: B. localStorage for the cart (persists indefinitely across tabs and browser restarts, until explicitly cleared) and sessionStorage for the scroll notice (persists only for the lifetime of that specific TAB — cleared automatically when the tab is closed, but survives a same-tab refresh)
ExplanationlocalStorage and sessionStorage share the same simple key-value API, but differ in LIFETIME and SCOPE. localStorage data persists indefinitely on the user's device, shared across every tab/window for that origin, until a script or the user explicitly clears it — perfect for a shopping cart that should survive closing and reopening the browser entirely. sessionStorage data is scoped to a single tab's lifetime: it survives a page refresh within that same tab (since the tab itself hasn't closed), but is automatically wiped the moment that specific tab is closed, and is NOT shared with other tabs even on the same site — exactly matching a 'don't show this notice again this visit, but do show it again if they come back later' requirement. Cookies, the third common mechanism, differ from both by being sent with every HTTP request to the server (adding overhead) and having an explicit, settable expiration date rather than being purely tab- or persistence-scoped.
Question 218 · Big-O of Chained Array Operations · medium
An array-processing task runs .map() then .filter() then .reduce() in sequence on an array of 10,000 numbers, each just doing simple arithmetic. What is the overall time complexity of this three-step chain, and why?
O(n^3), because three separate operations are chained together, so their costs multiply
O(n) — each of .map(), .filter(), and .reduce() individually makes ONE pass over its input array (each doing constant work per element), so chaining three O(n) passes gives O(n) + O(n) + O(n) = O(3n), which simplifies to O(n) in Big-O notation (constant factors like the '3' are dropped, since Big-O describes GROWTH RATE, not exact operation count)
O(log n), because array methods use an internal binary search
O(1), because modern JavaScript engines optimize chained array methods into a single pass automatically, with zero cost
Answer: B. O(n) — each of .map(), .filter(), and .reduce() individually makes ONE pass over its input array (each doing constant work per element), so chaining three O(n) passes gives O(n) + O(n) + O(n) = O(3n), which simplifies to O(n) in Big-O notation (constant factors like the '3' are dropped, since Big-O describes GROWTH RATE, not exact operation count)
ExplanationEach of .map(), .filter(), and .reduce() is independently O(n): each visits every element of its input array exactly once, doing a constant amount of work per element (assuming the per-element operation itself, like simple arithmetic, is O(1)). Chaining three separate O(n) passes gives a total cost of O(n) + O(n) + O(n) = O(3n). Big-O notation specifically describes how runtime GROWS as input size grows, and constant multipliers (like the 3 here) don't change that growth pattern — doubling n still roughly doubles the total work either way, so O(3n) is written simply as O(n). This is different from NESTING operations (like calling .map() INSIDE a .forEach() over the same array), which would multiply rather than add the costs, giving a true O(n^2). Note real engines do NOT automatically fuse chained .map/.filter/.reduce into one pass by default — each call to genuinely does create its own intermediate array (a real, if often acceptable, extra cost) — but the asymptotic time complexity remains linear either way.
You need to search for a specific student ID within a list, and the list is NOT sorted in any particular order. Can binary search be used here, and if not, how would you evaluate the correct approach and its complexity?
Yes, binary search works on any list regardless of order, in O(log n) time
No — binary search REQUIRES the list to be sorted, because its core logic ('compare the target to the middle element; if smaller, the target (if present) must be in the left half; if larger, it must be in the right half') only holds when the data is ordered; on an unsorted list you must use LINEAR SEARCH instead, checking each element one by one, which is O(n)
Binary search works but only if the list has an even number of elements
You must first convert the list to a dictionary before any search is possible
Answer: B. No — binary search REQUIRES the list to be sorted, because its core logic ('compare the target to the middle element; if smaller, the target (if present) must be in the left half; if larger, it must be in the right half') only holds when the data is ordered; on an unsorted list you must use LINEAR SEARCH instead, checking each element one by one, which is O(n)
ExplanationBinary search's efficiency comes entirely from being able to eliminate HALF the remaining candidates with each comparison — but that elimination logic ('if the target is smaller than the middle element, it cannot possibly be anywhere in the right half') is only valid when the data is sorted. On an unsorted list, a middle element being larger than your target tells you NOTHING about which half the target might be in — it could be anywhere. Attempting binary search on unsorted data would give incorrect, unreliable results, because the halving logic silently assumes an ordering that isn't actually there. The correct approach for unsorted data is linear search: check every element one at a time until you find a match or exhaust the list, which is O(n) — slower than binary search's O(log n), but it's the correct choice when sortedness isn't guaranteed (or when sorting first would cost more than a single linear scan is worth).
A hash map starts with 8 buckets. As more keys are inserted, the LOAD FACTOR (number of keys / number of buckets) climbs toward 1.0. Why do most hash map implementations automatically RESIZE (allocate more buckets and redistribute all existing keys) once the load factor crosses a threshold like 0.75, rather than just letting it keep growing?
Resizing is purely cosmetic and has no effect on performance
As load factor rises, more keys are forced to share the same bucket (collisions), and with separate chaining that means longer chains to scan per lookup — resizing to more buckets, then re-distributing every key using the new bucket count, keeps the average chain length short, preserving the O(1) average-case lookup that makes hash maps useful in the first place; without resizing, performance would gradually degrade toward O(n)
Resizing is required because Python dictionaries have a hard-coded maximum of 8 keys
Resizing only happens when a key is deleted, never when one is inserted
Answer: B. As load factor rises, more keys are forced to share the same bucket (collisions), and with separate chaining that means longer chains to scan per lookup — resizing to more buckets, then re-distributing every key using the new bucket count, keeps the average chain length short, preserving the O(1) average-case lookup that makes hash maps useful in the first place; without resizing, performance would gradually degrade toward O(n)
ExplanationA hash map's O(1) average-case performance depends on each bucket holding, on average, only a small, roughly-constant number of keys. As the load factor (keys/buckets) climbs, collisions become more frequent and each bucket's chain (under separate chaining) grows longer — a lookup that has to scan a bucket with 10 entries instead of 1 is doing meaningfully more work, degrading average performance toward the O(n) worst case. Resizing — typically DOUBLING the bucket count once load factor crosses a threshold like 0.75 — restores headroom: with twice as many buckets, the same keys redistribute (via re-hashing, since bucket assignment depends on the bucket COUNT) into shorter average chains again. This resize-and-rehash operation is itself O(n) when it happens, but because it happens rarely (only when the threshold is crossed, and each resize roughly doubles capacity), the AMORTIZED cost per insertion averaged over many insertions stays O(1) — the same trick behind Python list's automatic over-allocation when appending.
Question 221 · Promise.all() vs Sequential Await · medium
You need to fetch a user's profile, their recent orders, and their notification count — three INDEPENDENT API calls with no dependency between them. Compare: (A) await fetch(profile); await fetch(orders); await fetch(notifications); versus (B) await Promise.all([fetch(profile), fetch(orders), fetch(notifications)]). Which is faster, and why?
They take exactly the same total time, since the same three network requests happen either way
Option B is faster — awaiting each fetch SEQUENTIALLY in option A means the second request doesn't even START until the first one fully completes, making total time roughly the SUM of all three individual request times; Promise.all() in option B starts all three requests CONCURRENTLY (nearly simultaneously) and waits for all to finish, making total time roughly equal to the SLOWEST single request, not the sum
Option A is faster because sequential code always executes more predictably
Neither approach can run more than one network request at a time in JavaScript
Answer: B. Option B is faster — awaiting each fetch SEQUENTIALLY in option A means the second request doesn't even START until the first one fully completes, making total time roughly the SUM of all three individual request times; Promise.all() in option B starts all three requests CONCURRENTLY (nearly simultaneously) and waits for all to finish, making total time roughly equal to the SLOWEST single request, not the sum
ExplanationSince the three requests here have NO dependency on each other (none needs the result of another to begin), sequentially awaiting them one at a time is needlessly slow: await fetch(profile) fully blocks progress until it resolves, and only THEN does the code even begin the orders request, and so on — if each request takes roughly 200ms, the sequential total is roughly 200+200+200=600ms. Promise.all([...]) instead KICKS OFF all three fetch() calls essentially back-to-back (each starts immediately, without waiting for the others), and then waits for every one of them to resolve; since they're now running concurrently rather than one-after-another, total time is roughly bounded by whichever single request takes longest — around 200ms in this example, not 600ms. This is one of the most impactful, easy real-world async-JavaScript optimizations: batch independent async operations with Promise.all() rather than awaiting them one at a time.