From 649bf4e8fc5ca03cb2a324bce00f7c2f55659f25 Mon Sep 17 00:00:00 2001 From: Nitesh Seram Date: Wed, 6 May 2026 10:12:24 +0530 Subject: [PATCH 1/2] feat: expand quiz answers for better SEO --- questions/describe-event-capturing/en-US.mdx | 102 +++++++++- .../en-US.mdx | 69 +++++++ .../en-US.mdx | 63 ++++++- questions/explain-event-delegation/en-US.mdx | 170 ++++++++++++++++- .../en-US.mdx | 178 ++++++++++++++++-- .../en-US.mdx | 76 ++++++++ .../en-US.mdx | 74 ++++++++ .../what-are-server-sent-events/en-US.mdx | 57 +++++- .../en-US.mdx | 20 +- .../en-US.mdx | 68 +++++++ .../en-US.mdx | 96 +++++++++- .../en-US.mdx | 115 +++++++++++ 12 files changed, 1049 insertions(+), 39 deletions(-) diff --git a/questions/describe-event-capturing/en-US.mdx b/questions/describe-event-capturing/en-US.mdx index 1ea41d5..afd9fcb 100644 --- a/questions/describe-event-capturing/en-US.mdx +++ b/questions/describe-event-capturing/en-US.mdx @@ -81,13 +81,105 @@ child.addEventListener('click', () => { As a result of stopping event propagation, only the `parent` event listener will be called when you click the "Click me!" button, and the `child` event listener will never be called because event propagation has stopped at the `parent` element. -## Uses of event capturing +## Predict the output: capture, target, bubble in order -Event capturing is rarely used as compared to event bubbling, but it can be used in specific scenarios where you need to intercept events at a higher level before they reach the target element. +The complete event flow is the part candidates most often get wrong. The same `click` event passes through every ancestor on the way down (capture), arrives at the target, then walks back up (bubble). Here is the full sequence in one runnable example: -- **Stopping event bubbling:** Imagine you have a nested element (like a button) inside a container element. Clicking the button might also trigger a click event on the container. By enabling event capturing on the container's event listener, you can capture the click event there and prevent it from traveling down to the button, potentially causing unintended behavior. -- **Custom dropdown menus:** When building custom dropdown menus, you might want to capture clicks outside the menu element to close the menu. Using `capture: true` on the `document` object allows you to listen for clicks anywhere on the page and close the menu if the click happens outside its boundaries. -- **Efficiency in certain scenarios:** In some situations, event capturing can be slightly more efficient than relying on bubbling. This is because the event doesn't need to propagate through all child elements before reaching the handler. However, the performance difference is usually negligible for most web applications. +```js live +const grandparent = document.createElement('div'); +const parent = document.createElement('div'); +const child = document.createElement('button'); +grandparent.appendChild(parent); +parent.appendChild(child); +document.body.appendChild(grandparent); + +// Capture handlers (third arg = true) +grandparent.addEventListener( + 'click', + () => console.log('1. grandparent capture'), + true, +); +parent.addEventListener('click', () => console.log('2. parent capture'), true); + +// Target handler (default: bubble phase) +child.addEventListener('click', () => console.log('3. target')); + +// Bubble handlers +parent.addEventListener('click', () => console.log('4. parent bubble')); +grandparent.addEventListener('click', () => + console.log('5. grandparent bubble'), +); + +child.click(); +// Output (in this exact order): +// 1. grandparent capture +// 2. parent capture +// 3. target +// 4. parent bubble +// 5. grandparent bubble +``` + +The full picture: events go down, then up. Capture handlers fire from the root toward the target; bubble handlers fire from the target back toward the root. The target's own handler runs in the middle. (Listeners on the target itself fire in registration order regardless of the `capture` argument; the capture/bubble distinction only applies to ancestors.) + +## Bubbling vs capturing comparison + +| | Capturing | Bubbling | +| --- | --- | --- | +| Phase order | First (down from root) | Last (up to root) | +| `useCapture` argument | `true` (or `{ capture: true }`) | `false` (the default) | +| Default behavior | Off; must opt in | On; every listener bubbles by default | +| Effect of `event.stopPropagation()` | Stops the event before it reaches the target | Stops the event before higher ancestors see it | +| Common use cases | Intercepting non-bubbling events; pre-empting child handlers; analytics | Most click, input, and change handlers; event delegation | + +## When to use the capture phase in real apps + +In practice, the capture phase is the right tool for three specific situations: + +1. **Delegating non-bubbling events.** `focus`, `blur`, `scroll`, and `mouseenter`/`mouseleave` do not bubble, but they are visible to ancestors during the capture phase. Adding `addEventListener('focus', handler, true)` to a form gives you a delegated focus listener for every input inside it. + + ```js + form.addEventListener( + 'focus', + (event) => { + highlightField(event.target); + }, + true, // capture: catches focus events before they stop at the input + ); + ``` + +2. **Pre-empting child handlers** for analytics or feature gates. The capture phase runs before any child's bubble handler, so a region-wide "intercept clicks" listener can record the click (or block the action with `stopPropagation()`) before component code sees it. + +3. **Modal libraries that need first-look at clicks.** A modal dialog often listens at the document level with `capture: true` for outside-click dismissal. Using the bubble phase would let inner handlers call `stopPropagation()` and accidentally prevent the modal from closing. + +## `stopPropagation()` in capture vs bubble + +`stopPropagation()` blocks all subsequent phases, not just the next ancestor. + +```js live +const outer = document.createElement('div'); +const inner = document.createElement('button'); +outer.appendChild(inner); +document.body.appendChild(outer); + +outer.addEventListener( + 'click', + (event) => { + console.log('outer capture: stopping here'); + event.stopPropagation(); + }, + true, +); +inner.addEventListener('click', () => console.log('inner target')); +outer.addEventListener('click', () => console.log('outer bubble')); + +inner.click(); +// Output: 'outer capture: stopping here' +// The target handler and bubble handler are both skipped. +``` + +Calling `stopPropagation()` during the capture phase prevents the target's own handlers and every bubble-phase ancestor from running. This is useful for an "intercept and replace" pattern, but if the target needs to keep working, listen during the bubble phase instead. + +There is also `event.stopImmediatePropagation()`, which additionally prevents other listeners on the same element (registered in the same phase) from firing. Use it when multiple scripts add listeners to the same element and only one of them should run. ## Further reading diff --git a/questions/describe-the-difference-between-a-cookie-sessionstorage-and-localstorage/en-US.mdx b/questions/describe-the-difference-between-a-cookie-sessionstorage-and-localstorage/en-US.mdx index 59bc415..ea23592 100644 --- a/questions/describe-the-difference-between-a-cookie-sessionstorage-and-localstorage/en-US.mdx +++ b/questions/describe-the-difference-between-a-cookie-sessionstorage-and-localstorage/en-US.mdx @@ -137,6 +137,75 @@ sessionStorage.removeItem('key'); sessionStorage.clear(); ``` +## Security + +A side-by-side feature comparison hides the most important practical difference between these three: how each one behaves under XSS. + +| | Cookie | `localStorage` / `sessionStorage` | +| --- | --- | --- | +| Reachable from arbitrary JS on the origin | Only if not `HttpOnly` | Always | +| `HttpOnly` flag (cannot be read from JS) | Yes | No equivalent | +| `Secure` flag (HTTPS-only transport) | Yes | N/A (values never leave the client unless your code sends them) | +| `SameSite` flag (CSRF defense: `Strict`, `Lax`, `None`) | Yes | N/A | +| Partitioned cookies (CHIPS, isolated per top-level site) | Yes (modern browsers) | N/A | +| Survives an XSS exploit | An `HttpOnly` cookie does | All values are exfiltrable | + +The practical takeaways: + +- **Auth and session tokens belong in `HttpOnly; Secure; SameSite=Lax` cookies, not in `localStorage`.** Any XSS bug in any script on the origin can read everything in `localStorage`. An `HttpOnly` cookie cannot be read by JavaScript at all, so the attacker would have to make requests through the user's browser, which CSRF defenses (`SameSite`, double-submit tokens) are designed to block. +- **CSRF vs XSS is the trade-off.** Cookies need CSRF protection; `localStorage` tokens (with the Authorization-header pattern) do not. The cookie plus `SameSite=Lax` combination is generally considered safer because it survives XSS, and `SameSite=Lax` blocks the most common CSRF cases by default. +- **`localStorage` is fine for non-sensitive client state**: theme preferences, layout settings, draft text, recently viewed items. + +## Real-world set, get, and remove + +The three APIs look superficially similar but have meaningfully different ergonomics. Here is the same operation in each: + +```js +// localStorage: synchronous, string-only +localStorage.setItem('user', JSON.stringify({ id: 1, name: 'Ada' })); +const user = JSON.parse(localStorage.getItem('user') ?? 'null'); +localStorage.removeItem('user'); + +// sessionStorage: same shape, scoped to the tab +sessionStorage.setItem('draft', 'hello'); +const draft = sessionStorage.getItem('draft'); +sessionStorage.removeItem('draft'); + +// Cookie: modern async API (where supported) +await cookieStore.set({ + name: 'session', + value: 'abc123', + sameSite: 'Lax', + secure: true, + expires: Date.now() + 7 * 24 * 60 * 60 * 1000, +}); +const session = await cookieStore.get('session'); +await cookieStore.delete('session'); + +// Cookie: legacy `document.cookie` API (universally supported) +document.cookie = + 'session=abc123; Path=/; Max-Age=604800; SameSite=Lax; Secure'; +// Reading a specific cookie still requires parsing the string yourself: +const value = document.cookie + .split('; ') + .find((row) => row.startsWith('session=')) + ?.split('=')[1]; +``` + +A few common mistakes: + +- `localStorage` and `sessionStorage` only store strings. `localStorage.setItem('count', 0)` stores `"0"` and `localStorage.getItem('count')` returns `"0"` (a string). Always serialize and deserialize explicitly. +- Assigning `document.cookie = '...'` does not clear other cookies. Each assignment sets or updates a single cookie. To delete one, set it again with `Max-Age=0` or an `expires` date in the past. +- `cookieStore.set` is async; `localStorage.setItem` is synchronous. Mixing them in the same logic without awaiting the cookie call leads to ordering bugs. + +## Beyond these three: IndexedDB and Cache Storage + +Modern apps frequently need more than what these three APIs offer. Two more are worth knowing: + +- **IndexedDB**: an in-browser, asynchronous, transactional database. Use it for large structured data (offline app state, large user-generated content, search indexes), MBs to GBs of storage, and queryable data. Wrappers like [Dexie.js](https://dexie.org/) and [idb](https://github.com/jakearchibald/idb) make the API more pleasant. +- **Cache Storage** (`caches`): paired with [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), this stores HTTP request/response pairs for offline-capable apps and PWAs. It is not a general-purpose key-value store; it is specifically for caching network responses. +- **`localStorage` is for simple key-value config only.** If you find yourself JSON-stringifying complex nested data into `localStorage`, IndexedDB is usually a better fit. + ## Notes There are also other client-side storage mechanisms like [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) which is more powerful than the above-mentioned technologies but more complicated to use. diff --git a/questions/describe-the-difference-between-script-async-and-script-defer/en-US.mdx b/questions/describe-the-difference-between-script-async-and-script-defer/en-US.mdx index 114df87..5227edc 100644 --- a/questions/describe-the-difference-between-script-async-and-script-defer/en-US.mdx +++ b/questions/describe-the-difference-between-script-async-and-script-defer/en-US.mdx @@ -10,13 +10,13 @@ All of these ways (` +``` + +Behavior: + +- **Parsing**: deferred. HTML parsing continues; the script does not block. +- **Execution**: runs after the document has finished parsing. Across multiple ` +``` + +## Which to use: a decision matrix + +| Script type | Use for | +| --- | --- | +| `` for the runtime and chunk entries. CRA itself is deprecated, and new projects should use Vite or Next.js. + +## Common bugs from the wrong attribute choice + +- **Analytics with `defer` instead of `async`.** `defer` waits for HTML parsing, so a third-party tag at the top of the page artificially extends `DOMContentLoaded`. Use `async` for any independent third-party tag. +- **App entry as `async` script.** If `app.js` and `vendor.js` are loaded with `async`, they can execute in any order. `app.js` may run before `vendor.js` finishes, which throws `ReferenceError` for the missing globals. Use `defer` (or modules) for app scripts. +- **`document.write` inside a `defer` or `async` script.** Browsers ignore `document.write()` calls from async or deferred scripts with a console warning: "A call to document.write() from an asynchronously-loaded external script was ignored." The script would need to be a regular blocking `