Skip to content

Frontend Development Interview Questions & Answers

All 200 unique questions in one place — 60 easy, 95 medium, 45 hard — grouped by topic.

Practise by topic

Solved overallStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

What is the difference between ES modules and CommonJS?

Save to ▾
Show answer
ES modules use import and export, are statically analysable, load asynchronously and are the standard in browsers and modern Node. CommonJS uses require and module.exports, loads synchronously and was Node's original system. Because ES module imports are static, bundlers can tree-shake unused exports, which shrinks bundles. Mixing the two causes interop issues, so new frontend projects use ES modules with type: module in package.json.
02

How does destructuring with default values work?

Save to ▾
Show answer
Destructuring pulls values out of arrays or objects into variables. Defaults apply only when the value is undefined, not when it is null. For example, const { name, city = 'Delhi' } = user sets city to Delhi if the property is missing. Function parameters can be destructured too, like function render({ title, size = 'md' } = {}), which avoids errors when no argument is passed and documents expected options clearly.
03

Explain optional chaining and the nullish coalescing operator.

Save to ▾
Show answer
Optional chaining ?. stops evaluation and returns undefined if the value before it is null or undefined, so user?.address?.pincode never throws. Nullish coalescing ?? returns the right side only when the left is null or undefined, unlike || which also replaces 0, false and empty strings. For example, const qty = input ?? 1 keeps a valid 0, while input || 1 would wrongly change 0 into 1.
04

When should you use Map and Set instead of plain objects and arrays?

Save to ▾
Show answer
Map allows keys of any type, keeps insertion order, has a size property and performs well with frequent additions and deletions, so it suits caches and lookups by object. Set stores unique values with fast has() checks, ideal for tracking selected ids. Plain objects are better for fixed-shape records and JSON. Arrays are better when order and index access matter and duplicates are allowed.
05

What are pure functions and why does immutability matter in frontend code?

Save to ▾
Show answer
A pure function returns the same output for the same input and has no side effects such as changing external variables. Immutability means creating new objects instead of modifying existing ones, for example using spread syntax to update state. Together they make code predictable and easy to test, and they let frameworks detect changes with a simple reference comparison, which enables efficient re-rendering.
06

What are higher-order functions and currying?

Save to ▾
Show answer
A higher-order function takes a function as an argument or returns one, like map, filter or a custom withLogging(fn) wrapper. Currying transforms a function with many arguments into a chain of single-argument functions, so add(2)(3) returns 5. Currying makes partial application easy, for example creating formatINR = formatCurrency('INR') once and reusing it. Overusing it can reduce readability for teams unfamiliar with functional style.
07

Flatten a deeply nested array without using Array.prototype.flat.

Save to ▾
Show answer
A recursive reduce handles any depth: function flatten(arr) { return arr.reduce((acc, item) => Array.isArray(item) ? acc.concat(flatten(item)) : acc.concat(item), []); } flatten([1, [2, [3, [4]], 5]]); // [1, 2, 3, 4, 5] In real code, arr.flat(Infinity) does the same. For extremely deep nesting, an iterative version with a stack avoids call stack overflow.
08

Predict the output order of synchronous code, promises and setTimeout.

Save to ▾
Show answer
Synchronous code runs first, then microtasks, then macrotasks: console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); queueMicrotask(() => console.log('D')); console.log('E'); // Output: A, E, C, D, B A and E are synchronous. C and D are microtasks queued in order. B is a macrotask and waits until the microtask queue is empty, even with a 0 ms delay.
09

Editing one item in a copied list also changes the original list. Why?

Save to ▾
Show answer
Copying an array with spread or slice creates a new array, but the objects inside are still the same references. Changing copy[0].qty changes the original object too. The fix is to create new objects for changed items, for example list.map(item => item.id === id ? { ...item, qty: item.qty + 1 } : item), or use structuredClone when a full deep copy is genuinely needed.
10

Why do you want to build a career in frontend development?

Save to ▾
Show answer
I like work where users see the result directly. In college I built a timetable app for my class, and watching classmates use it daily taught me how small UI details matter. Frontend combines logic, design and performance, and the ecosystem keeps evolving, which keeps me learning. I want to build fast, accessible interfaces for products used by millions of people across India.
11

Compare Promise.all, Promise.allSettled, Promise.race and Promise.any.

Save to ▾
Show answer
Promise.all resolves with all results when every promise succeeds, but rejects immediately if any fails. Promise.allSettled always waits for all and returns each outcome with status fulfilled or rejected, useful for dashboards loading independent widgets. Promise.race settles with the first promise to settle, often used for timeouts. Promise.any resolves with the first success and rejects only if all fail, useful for trying multiple mirrors.
12

Why does await inside forEach not wait for each item?

Save to ▾
Show answer
forEach ignores the promise returned by an async callback, so all callbacks start almost at once and forEach finishes before any await completes. Code after it runs too early. For sequential processing, use a for...of loop with await inside. For parallel processing with a final result, use await Promise.all(items.map(async item => ...)). Choosing between them depends on whether order and rate limits matter.
13

What are tagged template literals?

Save to ▾
Show answer
A tagged template is a function placed before a template literal, like html`<p>${name}</p>`. The function receives the static string parts as an array and the interpolated values separately, so it can escape, transform or validate values before joining them. Libraries use this pattern for safe HTML templating, CSS-in-JS and GraphQL queries. Normal template literals simply interpolate values and support multi-line strings.
14

How do private class fields and getters work in modern JavaScript?

Save to ▾
Show answer
Fields prefixed with #, such as #balance, are truly private and cannot be accessed outside the class body, unlike the old underscore convention. Getters and setters, written get total() and set total(v), let you expose computed or validated properties while keeping internal state hidden. Static fields and static blocks are also supported. These features make classes safer for building reusable UI components and service objects.
15

Implement a throttle function.

Save to ▾
Show answer
Throttle allows a function to run at most once in a given interval: function throttle(fn, wait) { let last = 0; return function (...args) { const now = Date.now(); if (now - last >= wait) { last = now; fn.apply(this, args); } }; } window.addEventListener('scroll', throttle(updateProgressBar, 100)); Unlike debounce, which waits for calls to stop, throttle fires regularly during continuous events such as scrolling or dragging.
16

Write a fetch wrapper that retries with exponential backoff.

Save to ▾
Show answer
Retry on network errors and 5xx responses, doubling the delay each time: async function fetchWithRetry(url, options = {}, retries = 3, delay = 500) { try { const res = await fetch(url, options); if (res.status >= 500 && retries > 0) throw new Error('Server ' + res.status); return res; } catch (err) { if (retries === 0) throw err; await new Promise(r => setTimeout(r, delay)); return fetchWithRetry(url, options, retries - 1, delay * 2); } } Do not retry 4xx errors, and only retry idempotent requests.
17

Cancel an older search request when the user types again using AbortController.

Save to ▾
Show answer
Keep a controller per request and abort the previous one: let controller; async function search(term) { controller?.abort(); controller = new AbortController(); try { const res = await fetch('/api/search?q=' + encodeURIComponent(term), { signal: controller.signal }); renderResults(await res.json()); } catch (err) { if (err.name !== 'AbortError') showError(err); } } This prevents slow old responses from overwriting newer results.
18

Search results sometimes show data for an older query. What is happening?

Save to ▾
Show answer
This is a race condition: requests for 'ja' and 'java' are in flight together, and if the older one responds last, it overwrites the newer results. I would cancel the previous request with AbortController, or tag each request with an id and ignore responses that are not the latest. Debouncing the input reduces the number of requests, and caching repeated queries makes responses faster and more consistent.
19

Users in India see order dates shifted by a day. What is the likely bug?

Save to ▾
Show answer
The server probably sends a date like 2026-11-12 without time zone, and new Date('2026-11-12') treats date-only strings as UTC midnight, while other formats are parsed as local time, causing shifts around midnight. I would send full ISO timestamps with offsets, store times in UTC, and format for display with Intl.DateTimeFormat('en-IN', { timeZone: 'Asia/Kolkata' }). The Temporal API, now arriving in browsers, makes this safer.
20

Errors from failed promises are silently disappearing in production. How do you catch them?

Save to ▾
Show answer
Promises without a catch produce unhandled rejections that do not stop the app but hide failures. I would add a global window.addEventListener('unhandledrejection') handler and window.onerror that report errors to a monitoring tool like Sentry with source maps. In code, every async call should have try and catch or a .catch, and ESLint rules such as no-floating-promises in TypeScript projects catch missing handlers.
21

How would you explain a difficult JavaScript concept to a junior teammate?

Save to ▾
Show answer
Situation: a junior developer kept writing bugs with async code. Task: I wanted them to understand promises rather than copy fixes. Action: I drew the event loop on a whiteboard, wrote three small console examples for them to predict, and then reviewed one of their real pull requests together. Result: within two weeks, their async bugs stopped and they started explaining the concept to the next new joiner.
22

What are WeakMap and WeakRef, and when are they useful?

Save to ▾
Show answer
A WeakMap holds object keys weakly, so when no other reference to the key exists, the entry can be garbage collected. This makes it ideal for attaching private metadata or caching computed data for DOM nodes without causing memory leaks. WeakMap keys are not enumerable. WeakRef holds a weak reference to a single object and is rarely needed, mostly for caches, because garbage collection timing is unpredictable.
23

What are Proxy and Reflect used for?

Save to ▾
Show answer
A Proxy wraps an object and intercepts operations like get, set, has and deleteProperty through handler traps. Reflect provides matching methods that perform the default behaviour, so traps can add logic and then call Reflect.set. Reactive frameworks such as Vue 3 use Proxies to track which properties a component reads and re-render when they change. Proxies add overhead, so they suit framework internals more than hot loops.
24

Write a polyfill for Promise.all.

Save to ▾
Show answer
Resolve when all succeed, keep result order, reject on first failure: function promiseAll(items) { return new Promise((resolve, reject) => { const results = []; let done = 0; if (items.length === 0) return resolve(results); items.forEach((item, i) => { Promise.resolve(item).then(value => { results[i] = value; if (++done === items.length) resolve(results); }, reject); }); }); } Promise.resolve handles plain values mixed with promises.
25

Parsing a 30 MB JSON response freezes the page for seconds. How do you fix it?

Save to ▾
Show answer
JSON.parse runs on the main thread, so a huge payload blocks rendering and input. The best fix is to avoid sending that much: paginate, filter on the server or request only needed fields. If the data is truly needed, parse and process it inside a Web Worker and send back only what the UI needs, or stream it with NDJSON and process chunks. Compression reduces transfer time but not parse time.
26

What is TypeScript and why do teams adopt it?

Save to ▾
Show answer
TypeScript is a superset of JavaScript that adds static types and compiles to plain JavaScript. It catches errors such as wrong property names, missing arguments and null access while you type, before the code runs. It also improves editor autocomplete and makes refactoring large codebases safer. Types are erased at runtime, so TypeScript adds no performance cost but needs a build or type-stripping step.
27

What is the difference between type and interface in TypeScript?

Save to ▾
Show answer
Both describe object shapes. An interface can be extended with extends and merged by declaring it twice, which libraries use to let users augment types. A type alias can describe anything, including unions, tuples, primitives and mapped types, and combines with intersections. For plain object shapes either works; many teams use interface for public object contracts and type for unions and computed types.
28

What are union and intersection types?

Save to ▾
Show answer
A union type A | B means the value can be either type, such as string | number or 'success' | 'error'. You must narrow it before using type-specific members. An intersection type A & B combines all properties of both, for example User & { permissions: string[] } adds a field to an existing type. Unions model alternatives, while intersections model composition of features.
29

Explain the utility types Partial, Pick, Omit and Record.

Save to ▾
Show answer
Partial<T> makes all properties optional, useful for update payloads. Pick<T, 'id' | 'name'> creates a type with only selected keys, and Omit<T, 'password'> removes keys, handy for public user objects. Record<K, V> builds an object type with keys K and values V, such as Record<'en' | 'hi', string> for translations. Others include Required, Readonly, ReturnType and Awaited.
30

How would you learn TypeScript quickly if your new project required it?

Save to ▾
Show answer
Situation: I joined an internship where the codebase was fully TypeScript, which I had never used. Task: I needed to contribute within the first week. Action: I read the official handbook basics, studied existing types in the project, and converted small JavaScript exercises into TypeScript each evening. Result: I submitted my first typed component on day five and became comfortable with generics within a month.
31

Explain any, unknown and never.

Save to ▾
Show answer
any turns off type checking completely, so errors slip through silently. unknown also accepts any value, but you must narrow it with typeof, instanceof or a type guard before using it, which makes it the safe choice for API responses and catch errors. never represents values that never occur, such as a function that always throws, or the leftover case in an exhaustive switch over a union.
32

How does type narrowing work in TypeScript?

Save to ▾
Show answer
Narrowing is how TypeScript refines a broad type to a specific one inside a code branch. It understands typeof checks, instanceof, the in operator, equality checks, truthiness and discriminant properties. For example, inside if (typeof value === 'string') you can call value.toUpperCase(). Custom type guard functions returning value is User extend this to your own checks, and assertion functions narrow after they run.
33

What are generics and why are they useful?

Save to ▾
Show answer
Generics let you write reusable code that keeps type information, using type parameters like <T>. For example, function first<T>(items: T[]): T | undefined returns a number for a number array and a User for a User array without losing types. Constraints such as <T extends { id: string }> restrict what T can be. Generics power typed API clients, form libraries and reusable UI components.
34

Should you use enums or string literal unions?

Save to ▾
Show answer
TypeScript enums generate runtime JavaScript objects, and numeric enums accept any number, which weakens safety. String literal unions like type Status = 'draft' | 'published' have zero runtime cost, work naturally with JSON and give the same autocomplete. When you need a runtime list of values, an as const object plus a derived type works well. Many teams avoid enums, especially since type-stripping tools do not support them.
35

Which tsconfig options are important for a strict codebase?

Save to ▾
Show answer
strict: true enables a family of checks, including strictNullChecks and noImplicitAny, and should be on for new projects. noUncheckedIndexedAccess adds undefined to array and record lookups, catching many runtime errors. exactOptionalPropertyTypes, noImplicitOverride and noFallthroughCasesInSwitch add more safety. For bundler-based apps, moduleResolution: bundler, isolatedModules and verbatimModuleSyntax keep TypeScript aligned with how tools like Vite compile code.
36

What are declaration files (.d.ts) and when do you write them?

Save to ▾
Show answer
Declaration files describe the types of JavaScript code without containing implementation. Libraries ship them so TypeScript users get type checking, and many come from DefinitelyTyped as @types packages. You write your own when using an untyped library, declaring global variables injected by scripts, or typing non-code imports like declare module '*.svg'. Generating them with declaration: true is standard when publishing a TypeScript library.
37

Write a type-safe getProperty function using generics and keyof.

Save to ▾
Show answer
Constrain the key to the object's own keys: function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const course = { title: 'Frontend', weeks: 14 }; const weeks = getProperty(course, 'weeks'); // number // getProperty(course, 'price'); // compile error T[K] is an indexed access type, so the return type matches the chosen property. Typos in key names are caught at compile time.
38

Model API request state with a discriminated union.

Save to ▾
Show answer
A shared status field lets TypeScript narrow each case: type ApiState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; message: string }; function render(state: ApiState<string[]>) { switch (state.status) { case 'success': return state.data.join(', '); case 'error': return 'Error: ' + state.message; default: return 'Loading...'; } } Impossible states, like having both data and an error, cannot be represented.
39

Create a typed fetch helper with generics.

Save to ▾
Show answer
Let callers specify the expected response type: async function getJson<T>(url: string): Promise<T> { const res = await fetch(url); if (!res.ok) throw new Error(`Request failed: ${res.status}`); return res.json() as Promise<T>; } interface Course { id: number; title: string; } const courses = await getJson<Course[]>('/api/courses'); courses[0].title.toUpperCase(); The cast trusts the server, so for untrusted data validate with a schema library like Zod before returning.
40

Write a custom type guard to check if a value is a User.

Save to ▾
Show answer
A function returning value is User narrows the type: interface User { id: number; email: string; } function isUser(value: unknown): value is User { return typeof value === 'object' && value !== null && 'id' in value && typeof (value as User).id === 'number' && 'email' in value && typeof (value as User).email === 'string'; } const data: unknown = JSON.parse(raw); if (isUser(data)) console.log(data.email); Inside the if block, data is treated as User.
41

Create a type where all fields are optional except id.

Save to ▾
Show answer
Combine Pick and Partial with an intersection: interface Product { id: string; name: string; price: number; stock: number; } type ProductUpdate = Pick<Product, 'id'> & Partial<Omit<Product, 'id'>>; const update: ProductUpdate = { id: 'p1', price: 799 }; // const bad: ProductUpdate = { price: 799 }; // error: id missing This is a common pattern for PATCH request payloads, ensuring the identifier is always present.
42

A third-party library you need has no TypeScript types. What do you do?

Save to ▾
Show answer
First I check if types exist on DefinitelyTyped by installing @types/library-name. If not, I write a minimal declaration file, for example declare module 'old-chart-lib' with only the functions I use, in a types folder included by tsconfig. This is better than a blanket any import. If the library is important to the community, contributing those types upstream helps everyone.
43

Your team uses 'as any' everywhere to silence errors. How do you improve this?

Save to ▾
Show answer
I would first understand why: often the real types are unclear or the API response is untyped. I would add shared API types, introduce unknown with type guards for external data, and enable the ESLint rule no-explicit-any as a warning. Then I would fix the most risky areas first and block new any in CI. Showing a bug that types would have caught helps get team buy-in.
44

Tell me about a time you convinced your team to adopt a new tool like TypeScript.

Save to ▾
Show answer
Situation: our JavaScript app kept breaking after API changes. Task: I believed TypeScript would help but the team worried about slowing down. Action: I converted one small module as a pilot, showed three bugs it caught, and proposed gradual adoption with no deadline pressure. Result: the team agreed, we migrated over two quarters, and production errors related to wrong data shapes dropped significantly.
45

What does structural typing mean in TypeScript?

Save to ▾
Show answer
TypeScript compares types by their shape, not by their declared names. If an object has all the properties a type requires, it is compatible, even if it was never declared as that type. This is flexible but can let a UserId string be passed where an OrderId string is expected. Teams use branded types, such as string & { __brand: 'UserId' }, to simulate nominal typing for important identifiers.
46

What does the satisfies operator do?

Save to ▾
Show answer
satisfies checks that a value matches a type without changing the value's inferred type. With const routes = { home: '/', about: '/about' } satisfies Record<string, string>, TypeScript validates the shape but still knows the exact keys, so routes.contact is an error. A plain type annotation would widen the type and lose those specific keys. It is ideal for configuration objects and theme tokens.
47

Write a DeepReadonly type using mapped and conditional types.

Save to ▾
Show answer
Recursively mark nested properties readonly: type DeepReadonly<T> = T extends (...args: any[]) => any ? T : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T; interface Config { api: { baseUrl: string; retries: number } } const config: DeepReadonly<Config> = { api: { baseUrl: '/api', retries: 3 } }; // config.api.retries = 5; // error Functions are left untouched, and primitives end the recursion.
48

How would you migrate a large JavaScript codebase to TypeScript?

Save to ▾
Show answer
I would migrate gradually rather than in one big rewrite. First add tsconfig with allowJs and checkJs off, rename entry files to .ts, and keep the build working. Then convert shared utilities and API types first, because many files depend on them, and enable strict for new files. I would track the number of any usages, add type checking to CI, and convert feature folders as they are touched.
49

TypeScript types say a field exists, but the app crashes because the API did not send it. How do you prevent this?

Save to ▾
Show answer
TypeScript only checks at compile time; it cannot guarantee what a server returns at runtime. I would validate responses at the boundary with a schema library like Zod or Valibot, deriving TypeScript types from the schema so they never drift. Invalid data then fails clearly with a logged error and a fallback UI. Generating types from OpenAPI specs also keeps frontend and backend contracts aligned.
50

Type checking takes several minutes in a large monorepo. How do you speed it up?

Save to ▾
Show answer
I would enable project references with composite builds so only changed packages are rechecked, and turn on incremental with build info files cached in CI. skipLibCheck avoids rechecking node_modules declarations. Very complex conditional types and huge unions slow the checker, so I would profile with --generateTrace and simplify hotspots. The bundler can use fast transpilation while type checking runs separately in parallel.
51

Why use requestAnimationFrame instead of setTimeout for animations?

Save to ▾
Show answer
requestAnimationFrame runs your callback right before the browser paints the next frame, usually 60 or 120 times per second matching the display. It pauses in background tabs, saving battery, and avoids the drift and dropped frames that setTimeout causes because timers are not synced with rendering. The callback receives a high-precision timestamp, so animations can be calculated from elapsed time rather than fixed steps.
52

What is a Progressive Web App?

Save to ▾
Show answer
A Progressive Web App is a website that behaves like an installable app. It needs HTTPS, a web app manifest with name, icons and theme colour, and usually a service worker for offline support and caching. Users can add it to their home screen and it opens in a standalone window. PWAs are popular in India for lightweight apps that work on low-end phones and patchy mobile networks.
53

How do browser permission APIs like Geolocation and Notifications work?

Save to ▾
Show answer
Powerful features require user consent, a secure HTTPS context and usually a user gesture. Calling navigator.geolocation.getCurrentPosition or Notification.requestPermission shows a browser prompt; if the user denies it, you cannot ask again easily. navigator.permissions.query lets you check the current state first. Good UX explains why you need the permission before triggering the prompt, for example asking for location only when the user taps 'Find nearest centre'.
54

What are the URL and URLSearchParams APIs used for?

Save to ▾
Show answer
The URL constructor parses a URL into parts like hostname, pathname and searchParams, and builds valid URLs without manual string concatenation. URLSearchParams reads and writes query strings, handling encoding automatically, for example params.get('page') or params.set('sort', 'price'). They prevent bugs with special characters such as spaces and ampersands in search terms, and are the clean way to sync filters with the address bar.
55

Keep product filters in sync with the URL query string.

Save to ▾
Show answer
Read params on load and update them without reload: const params = new URLSearchParams(location.search); const category = params.get('category') ?? 'all'; applyFilters({ category }); function updateFilter(key, value) { const p = new URLSearchParams(location.search); value ? p.set(key, value) : p.delete(key); history.pushState(null, '', '?' + p.toString()); applyFilters(Object.fromEntries(p)); } window.addEventListener('popstate', () => applyFilters(Object.fromEntries(new URLSearchParams(location.search)))); Users can now share and bookmark filtered views.
56

Implement a copy-to-clipboard button with user feedback.

Save to ▾
Show answer
The async Clipboard API works in secure contexts after a user action: copyBtn.addEventListener('click', async () => { try { await navigator.clipboard.writeText(couponCode.textContent); copyBtn.textContent = 'Copied!'; } catch { copyBtn.textContent = 'Press Ctrl+C to copy'; } setTimeout(() => (copyBtn.textContent = 'Copy code'), 2000); }); An aria-live region can announce 'Copied' to screen reader users, since a changing button label may not always be read out.
57

A new browser API you used does not work in older Safari versions. How do you handle it?

Save to ▾
Show answer
I would use feature detection, such as if ('share' in navigator), rather than browser sniffing, and provide a fallback like a copy-link button. Checking Can I Use and the project's browser support list before building avoids surprises. Where reasonable, a small polyfill can fill gaps. This progressive enhancement approach ensures the core feature works everywhere while newer browsers get a better experience.
58

Tell me about a time you learned a new browser API to deliver a feature.

Save to ▾
Show answer
Situation: during my internship, the team wanted product images to load only when visible. Task: I was asked to implement it without adding a library. Action: I studied IntersectionObserver on MDN, built a small prototype, measured the page in Lighthouse and then applied it to the listing page with a fallback. Result: initial page weight dropped by about 60 percent, and the pattern was reused on other pages.
59

What is the difference between a Web Worker and a Service Worker?

Save to ▾
Show answer
A Web Worker runs JavaScript on a background thread for a single page, used for heavy computations like parsing files or image processing without freezing the UI. A Service Worker sits between the page and the network for an entire origin, intercepting requests to enable offline support, caching and push notifications. It has a lifecycle of install, activate and fetch, and it works only over HTTPS.
60

Compare IntersectionObserver, ResizeObserver and MutationObserver.

Save to ▾
Show answer
IntersectionObserver reports when an element enters or leaves the viewport or a container, used for lazy loading, infinite scroll and analytics impressions. ResizeObserver reports changes to an element's size, useful for charts that must redraw when their container changes. MutationObserver reports DOM changes like added nodes or changed attributes. All three are asynchronous and far more efficient than polling or scroll and resize listeners.
61

When should you use IndexedDB instead of localStorage?

Save to ▾
Show answer
localStorage is synchronous, stores only strings and is limited to about 5 MB, so large reads block the main thread. IndexedDB is asynchronous, stores structured data including blobs, supports indexes and transactions, and can hold hundreds of megabytes depending on disk space. Use it for offline data, drafts, cached API results or files in PWAs. Wrapper libraries like idb make its event-based API easier with promises.
62

How does the History API enable client-side routing?

Save to ▾
Show answer
history.pushState(state, '', '/courses/react') changes the URL and adds a history entry without reloading the page, and replaceState updates the current entry. When users press back or forward, the popstate event fires and the app renders the matching view. The server must return the app's HTML for all routes, otherwise refreshing a deep link gives a 404. Routers in SPA frameworks are built on this API.
63

What is the same-origin policy and how does postMessage work around it?

Save to ▾
Show answer
An origin is the combination of scheme, host and port. The same-origin policy stops scripts from reading data or DOM of another origin, such as an embedded iframe from a payment provider. window.postMessage lets two windows or iframes exchange messages safely across origins. The receiver must check event.origin against an allow-list, and the sender should specify the exact target origin instead of '*'.
64

How can you communicate between multiple tabs of the same site?

Save to ▾
Show answer
BroadcastChannel lets same-origin tabs, windows and workers publish and subscribe to messages on a named channel, which suits syncing logout, theme or cart updates. The storage event fires in other tabs when localStorage changes, which is a simple fallback. SharedWorker can hold shared state for all tabs. Each option works only within the same origin, so cross-domain sync needs a server or postMessage.
65

Move a heavy calculation into a Web Worker.

Save to ▾
Show answer
The worker runs on its own thread and talks through messages: // worker.js self.onmessage = (e) => { const total = e.data.reduce((sum, row) => sum + row.amount, 0); self.postMessage(total); }; // main.js const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }); worker.postMessage(bigArrayOfRows); worker.onmessage = (e) => showTotal(e.data); The UI stays responsive. Data is copied between threads, so for huge binary data use transferable objects like ArrayBuffer.
66

Detect offline status and retry queued requests when the connection returns.

Save to ▾
Show answer
Listen for online and offline events and keep a small queue: const queue = []; async function send(req) { if (!navigator.onLine) { queue.push(req); showBanner('You are offline'); return; } await fetch(req.url, req.options); } window.addEventListener('online', async () => { hideBanner(); while (queue.length) await send(queue.shift()); }); window.addEventListener('offline', () => showBanner('You are offline')); navigator.onLine can report true on captive networks, so treat failed fetches as offline too. Persist the queue in IndexedDB for reliability.
67

Create a simple custom element with Shadow DOM.

Save to ▾
Show answer
Extend HTMLElement and register a tag name containing a hyphen: class RatingStars extends HTMLElement { connectedCallback() { const value = Number(this.getAttribute('value') || 0); const root = this.attachShadow({ mode: 'open' }); root.innerHTML = `<style>span { color: var(--star-color, orange); }</style> <span role='img' aria-label='${value} out of 5'>${'*'.repeat(value)}</span>`; } } customElements.define('rating-stars', RatingStars); // usage: <rating-stars value='4'></rating-stars> The custom property lets the page theme it despite Shadow DOM isolation.
68

When a user logs out in one tab, other open tabs stay logged in. How do you fix it?

Save to ▾
Show answer
I would broadcast a logout message using BroadcastChannel, or the storage event as a fallback, so every tab clears its in-memory state and redirects to the login page. The server must also invalidate the session or refresh token, so stale tabs fail their next API call with a 401, which the app handles by logging out. Both client sync and server invalidation are needed.
69

In a single-page app, the back button takes users out of the site instead of to the previous filter. Why?

Save to ▾
Show answer
The app changes the view without adding history entries, so the browser has nothing to go back to within the site. Each meaningful navigation, like changing a category or opening a product, should call pushState or the router's navigate function, while minor changes such as typing in search can use replaceState. The app must also listen to popstate and restore the view and scroll position from the URL.
70

How do you handle unclear requirements from a product manager?

Save to ▾
Show answer
Situation: a product manager asked for 'offline support' in our web app without details. Task: I needed clarity before estimating. Action: I listed specific questions, which screens, read-only or editing, how long offline, and shared a quick prototype showing two options with effort estimates. Result: we agreed on read-only offline access for the order history, which shipped in one sprint instead of an open-ended project.
71

Explain common service worker caching strategies.

Save to ▾
Show answer
Cache-first serves from cache and falls back to network, ideal for versioned static assets. Network-first tries the network and falls back to cache, suited to HTML and API data that should be fresh. Stale-while-revalidate returns the cached copy immediately while fetching an update in the background, good for avatars or non-critical data. Libraries like Workbox implement these strategies and handle cache expiry and versioning.
72

What are Web Components and Shadow DOM?

Save to ▾
Show answer
Web Components are native, framework-independent reusable elements built from Custom Elements, Shadow DOM and HTML templates. Shadow DOM attaches an encapsulated DOM tree to an element, so its styles do not leak out and page styles do not leak in, except through CSS custom properties and ::part. They are used for design systems shared across React, Angular and plain HTML apps. Server rendering and form participation need extra work.
73

Register a service worker that caches static assets with a cache-first strategy.

Save to ▾
Show answer
Register from the page, then handle install and fetch in sw.js: // main.js if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js'); // sw.js const CACHE = 'static-v3'; self.addEventListener('install', e => { e.waitUntil(caches.open(CACHE).then(c => c.addAll(['/', '/app.css', '/app.js']))); }); self.addEventListener('fetch', e => { e.respondWith(caches.match(e.request).then(hit => hit || fetch(e.request))); }); Changing the cache name on each release lets the activate step delete old caches.
74

After a deployment, some users keep seeing the old version of your PWA. Why?

Save to ▾
Show answer
The old service worker is still controlling the page and serving cached files; a new worker waits until all tabs using the old one are closed. I would version the cache, delete old caches in the activate event, and show an 'Update available' prompt that calls skipWaiting and reloads. The sw.js file itself must be served with no-cache headers so browsers check for updates promptly.
75

Your page embeds a payment iframe from another domain and needs its result. How do you do it safely?

Save to ▾
Show answer
The iframe should send the result with parent.postMessage(data, 'https://yourshop.com'), and the page listens for message events, checking event.origin against the exact payment domain and validating the data shape before acting. The frontend message should only update UI; the final payment status must be confirmed from your server through the gateway's webhook or verification API, since messages can be spoofed.
76

How does Tailwind CSS keep the final CSS file small?

Save to ▾
Show answer
Tailwind scans your source files for class names and generates only the utilities you actually use, so the production CSS is usually a few kilobytes even though thousands of utilities are available. This requires class names to appear as complete strings in the code. Tailwind v4 uses a CSS-first configuration with @theme for design tokens and detects source files automatically.
77

Does native CSS support nesting now?

Save to ▾
Show answer
Yes. All modern browsers support native CSS nesting, so you can write .card { padding: 1rem; & .title { font-weight: 600; } &:hover { box-shadow: ... } } without Sass. The ampersand refers to the parent selector. Deep nesting still creates high specificity and long selectors, so the usual advice from Sass days applies: keep nesting to one or two levels.
78

Why are dvh and svh units better than vh on mobile?

Save to ▾
Show answer
On mobile browsers, 100vh equals the height with the address bar hidden, so a full-screen section gets cut off when the bar is visible. svh is the small viewport height with browser UI shown, lvh the large one, and dvh updates dynamically as the bar appears or hides. Using min-height: 100dvh for hero sections or app shells prevents content hiding behind browser chrome.
79

What colour contrast ratios does WCAG require?

Save to ▾
Show answer
WCAG 2.2 level AA requires a contrast ratio of at least 4.5 to 1 for normal text and 3 to 1 for large text, which is about 24 pixels regular or 19 pixels bold. Interface components and focus indicators need 3 to 1 against adjacent colours. Level AAA raises text to 7 to 1. Tools like the DevTools contrast checker help catch light grey text that fails these limits.
80

What is prefers-reduced-motion and why should you respect it?

Save to ▾
Show answer
prefers-reduced-motion is a media query that detects when users have asked their operating system to minimise animation, often because motion causes dizziness or nausea. Inside @media (prefers-reduced-motion: reduce), you should remove or tone down parallax, large transitions and auto-playing animations, while keeping essential feedback such as simple fades. It is a small change that makes interfaces accessible to people with vestibular disorders.
81

Write a visually-hidden utility class for screen-reader-only text.

Save to ▾
Show answer
This hides content visually but keeps it readable by assistive technology: .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } Use it for labels on icon-only buttons, such as <button><svg aria-hidden='true'></svg><span class='sr-only'>Close</span></button>. display: none would hide the text from screen readers too.
82

Show clear focus styles only for keyboard users.

Save to ▾
Show answer
:focus-visible applies when the browser decides focus should be shown, typically for keyboard navigation: button:focus-visible, a:focus-visible { outline: 3px solid #2563eb; outline-offset: 2px; } button:focus:not(:focus-visible) { outline: none; } Mouse users do not see an outline on click, while keyboard users always know where they are. Never remove outlines globally with *:focus { outline: none } without a replacement.
83

Make an embedded video keep a 16:9 ratio at any width.

Save to ▾
Show answer
The aspect-ratio property replaces the old padding-top hack: .video-embed { width: 100%; aspect-ratio: 16 / 9; border: 0; border-radius: 8px; } .avatar { width: 64px; aspect-ratio: 1; object-fit: cover; border-radius: 50%; } The browser reserves the correct height before the iframe loads, preventing layout shift, and object-fit keeps images from stretching inside fixed ratios.
84

A full-screen hero section is cut off at the bottom on mobile phones. How do you fix it?

Save to ▾
Show answer
The section uses height: 100vh, which on mobile includes the area hidden behind the browser's address bar. I would change it to min-height: 100svh or 100dvh so the height matches the visible viewport, keeping 100vh as a fallback line before it for very old browsers. Using min-height instead of height also lets content grow on small screens instead of overflowing.
85

Compare CSS Modules, CSS-in-JS and utility-first CSS.

Save to ▾
Show answer
CSS Modules scope class names per file at build time, giving plain CSS with no runtime cost. CSS-in-JS libraries write styles in JavaScript with dynamic props, but runtime versions add bundle size and slow server rendering, so zero-runtime options are now preferred. Utility-first frameworks like Tailwind compose small classes in markup, producing small CSS and consistent spacing. The choice depends on team preference and rendering strategy.
86

What are design tokens?

Save to ▾
Show answer
Design tokens are named values for design decisions, such as color-primary, space-4 or radius-md, stored in one place and shared by designers and developers. They are often kept in JSON and converted into CSS custom properties, Tailwind theme values or mobile platform formats. Changing a token updates every component consistently, which makes rebranding or dark mode much easier than hunting hard-coded values.
87

What does the :has() selector allow you to do?

Save to ▾
Show answer
:has() is often called the parent selector because it styles an element based on what it contains or what follows it. For example, .card:has(img) can change padding for cards with images, and form:has(input:invalid) can highlight a form with errors. It removes many cases where JavaScript was used only to toggle classes. It is supported in all modern browsers.
88

What are CSS logical properties?

Save to ▾
Show answer
Logical properties describe layout relative to the writing direction instead of physical sides. margin-inline-start means left in English but right in Arabic or Urdu, and padding-block maps to top and bottom in horizontal text. Using them, with the dir attribute on html, lets one stylesheet support both left-to-right and right-to-left languages without overrides. Sizes like inline-size replace width in the same way.
89

Animate a modal smoothly when it opens without causing layout work.

Save to ▾
Show answer
Animate only transform and opacity, which run on the compositor: .modal { opacity: 0; transform: translateY(16px) scale(0.98); transition: opacity 200ms ease, transform 200ms ease; } .modal.is-open { opacity: 1; transform: none; } @media (prefers-reduced-motion: reduce) { .modal { transition: opacity 100ms linear; transform: none; } } Animating top, height or margin instead would trigger layout on every frame and cause jank on low-end phones.
90

Define brand design tokens in Tailwind CSS v4.

Save to ▾
Show answer
Tokens go in CSS with the @theme directive: @import 'tailwindcss'; @theme { --color-brand-500: #2563eb; --color-brand-700: #1d4ed8; --font-display: 'Poppins', sans-serif; --radius-card: 12px; } /* usage in markup */ /* <button class='bg-brand-500 hover:bg-brand-700 rounded-card font-display'> */ Tailwind generates utilities like bg-brand-500 from these variables and also exposes them as CSS custom properties for use in plain CSS.
91

Write a card that changes layout based on its container width.

Save to ▾
Show answer
Use a container query so the card adapts wherever it is placed: .card-wrapper { container-type: inline-size; } .card { display: grid; gap: 1rem; } @container (min-width: 420px) { .card { grid-template-columns: 160px 1fr; align-items: center; } } In a narrow sidebar the image stacks above text; in a wide main column it sits beside the text, with no viewport media queries.
92

An accordion animation stutters on low-end Android phones. What is wrong?

Save to ▾
Show answer
Most likely it animates height, max-height or margin, which forces layout recalculation every frame. I would check the Performance panel for long layout events. Fixes include animating transform and opacity, using the newer interpolate-size or calc-size features for height where supported, or animating grid-template-rows from 0fr to 1fr, which is cheaper. Testing with CPU throttling in DevTools reveals these issues before users do.
93

Your app must now support an Arabic right-to-left version. What changes are needed in CSS?

Save to ▾
Show answer
I would set dir='rtl' on the html element for that locale and replace physical properties like margin-left, padding-right and left with logical ones such as margin-inline-start and inset-inline-start. Flexbox and Grid follow direction automatically. Directional icons like arrows need flipping with a transform in RTL, while logos and media do not. I would test layouts with real Arabic text, which has different line heights.
94

Tailwind classes built dynamically like 'bg-' + colour work locally but are missing in production. Why?

Save to ▾
Show answer
Tailwind finds classes by scanning source files for complete strings, so a class assembled at runtime never appears in the code and its CSS is not generated. The fix is to map values to full class names, such as const colours = { success: 'bg-green-600', error: 'bg-red-600' }. If truly dynamic values are needed, use inline styles with CSS variables or explicitly safelist those classes.
95

How do you handle a designer who expects pixel-perfect matching on every device?

Save to ▾
Show answer
Situation: a designer flagged small spacing differences between Figma and my build on several phones. Task: I needed to address concerns while keeping the layout fluid. Action: I fixed genuine mistakes immediately, then showed how fixed pixel values would break on other screens and proposed shared spacing tokens. Result: we agreed on tokens and breakpoints, and future handoffs had far fewer revision rounds.
96

Tell me about a time you received critical feedback in a code review.

Save to ▾
Show answer
Situation: a senior engineer rejected my pull request for a new dashboard because the CSS was hard to maintain. Task: I had to rework it without missing the sprint. Action: I asked for a short call to understand the concerns, refactored styles into reusable components with tokens, and added notes explaining my choices. Result: the PR was approved, and the components were later reused in two other screens.
97

What are CSS cascade layers and what problem do they solve?

Save to ▾
Show answer
Cascade layers, declared with @layer reset, base, components, utilities, let you control the order of priority between groups of styles regardless of selector specificity. A simple selector in a later layer beats a highly specific selector in an earlier layer. This solves specificity wars between resets, third-party libraries and your components, without resorting to !important. Unlayered styles win over all layered ones.
98

What is the difference between :is() and :where()?

Save to ▾
Show answer
Both take a list of selectors and match any of them, reducing repetition, for example :is(h1, h2, h3) a. The difference is specificity: :is() takes the specificity of its most specific argument, while :where() always has zero specificity. That makes :where() perfect for resets and library defaults that users should easily override, while :is() keeps normal priority.
99

Several teams contribute CSS to one large app and styles keep breaking each other. What do you propose?

Save to ▾
Show answer
I would introduce scoping so styles cannot leak, using CSS Modules or a consistent naming convention per team, and organise global styles with cascade layers for reset, tokens, components and utilities. A shared design token set and component library reduces one-off styles. Linting with Stylelint, visual regression tests in CI and clear ownership of global files prevent new conflicts.
100

You are asked to build a design system used by several products. How do you approach it?

Save to ▾
Show answer
I would start with an audit of existing UI to find common components and inconsistencies, then define design tokens with the design team. I would build accessible base components like button, input, modal and table, document them in Storybook with usage guidelines, and publish them as a versioned package. Semantic versioning, changelogs and visual regression testing help products upgrade safely.
101

What are Core Web Vitals and their good thresholds?

Save to ▾
Show answer
Core Web Vitals are Google's user experience metrics measured from real users. Largest Contentful Paint measures loading and should be 2.5 seconds or less. Interaction to Next Paint measures responsiveness and should be 200 milliseconds or less. Cumulative Layout Shift measures visual stability and should be 0.1 or less. A page passes when at least 75 percent of visits meet each threshold, and they influence search ranking.
102

What are code splitting and lazy loading?

Save to ▾
Show answer
Code splitting breaks a large JavaScript bundle into smaller chunks so the browser downloads only what the current page needs. Lazy loading defers loading code or assets until they are required, using dynamic import() for components like a chart or a rich text editor. Route-based splitting is the most common starting point. It reduces initial download and parse time, which improves LCP and INP on slower devices.
103

How do you choose the right image format and size?

Save to ▾
Show answer
Use AVIF or WebP for photos because they are much smaller than JPEG at similar quality, with JPEG as fallback. Use SVG for icons and logos, and PNG only when lossless transparency is essential. Serve sizes that match the display width with srcset and sizes, compress with tools like Squoosh or an image CDN, and always specify width and height to avoid layout shift.
104

How can web fonts slow a page down and how do you optimise them?

Save to ▾
Show answer
Fonts are often discovered late, after CSS is parsed, and text may stay invisible until they load. To optimise, self-host fonts in WOFF2 format, subset them to the characters you need, preload the main weight, and use font-display: swap so fallback text appears immediately. Limiting the number of weights and using variable fonts reduces downloads. Matching fallback metrics with size-adjust prevents layout shift.
105

Load a heavy module only when the user needs it.

Save to ▾
Show answer
Use a dynamic import inside the event handler: const exportBtn = document.querySelector('#export-pdf'); exportBtn.addEventListener('click', async () => { exportBtn.disabled = true; const { generatePdf } = await import('./pdf-export.js'); await generatePdf(reportData); exportBtn.disabled = false; }); The bundler creates a separate chunk for pdf-export.js, so users who never export do not download it. Prefetching on hover can hide the delay.
106

Add resource hints for a third-party font and the hero image.

Save to ▾
Show answer
Place these early in the head: <link rel='preconnect' href='https://fonts.gstatic.com' crossorigin> <link rel='preload' href='/fonts/inter-var.woff2' as='font' type='font/woff2' crossorigin> <link rel='preload' href='/img/hero-1200.avif' as='image' fetchpriority='high'> <link rel='dns-prefetch' href='https://analytics.example.com'> Fonts must be preloaded with crossorigin, even from the same origin, or the browser downloads them twice. Preload only a few critical resources.
107

Product images on a listing page load slowly. What quick fixes would you apply?

Save to ▾
Show answer
I would convert images to WebP or AVIF, resize them to the actual thumbnail size instead of loading full product photos, and serve them through srcset. Images below the fold get loading='lazy', while the first few visible ones load eagerly. Adding width and height prevents layout shift, and serving images from a CDN with long cache headers makes repeat visits much faster.
108

What is Interaction to Next Paint and what usually makes it poor?

Save to ▾
Show answer
INP replaced First Input Delay in 2024. It measures the delay from a click, tap or key press until the next frame is painted, across all interactions on the page, reporting roughly the worst one. Poor INP usually comes from long JavaScript tasks blocking the main thread, heavy event handlers, large re-renders and expensive layout work after interaction. Breaking up tasks and showing immediate visual feedback improves it.
109

What is the difference between lab data and field data in performance?

Save to ▾
Show answer
Lab data comes from controlled tests like Lighthouse or WebPageTest on a fixed device and network, which is great for debugging and comparing changes. Field data, or real user monitoring, comes from actual visitors through the Chrome UX Report or your own analytics, capturing real devices, networks and behaviour. Google ranks on field data. A page can score well in the lab but fail in the field on low-end phones.
110

Explain preload, prefetch and preconnect.

Save to ▾
Show answer
preconnect opens an early connection, including DNS, TCP and TLS, to an important third-party origin like a font or API server. preload fetches a resource needed for the current page at high priority, such as the hero image or main font. prefetch downloads resources likely needed for the next navigation at low priority during idle time. Overusing preload wastes bandwidth and can delay more critical resources.
111

What are long tasks and why do they hurt user experience?

Save to ▾
Show answer
A long task is any main-thread work lasting more than 50 milliseconds. While it runs, the browser cannot respond to taps or paint updates, so the page feels frozen. Common sources are large script evaluation, heavy JSON processing, big framework re-renders and third-party scripts. The Performance panel highlights them with red corners, and splitting work into smaller chunks or moving it to a worker keeps the page responsive.
112

How do HTTP caching headers work for frontend assets?

Save to ▾
Show answer
Cache-Control controls caching. Files with content hashes in their names, like app.3f9a2c.js, can use max-age=31536000, immutable because a new build produces a new filename. HTML should use no-cache, which forces revalidation so users get new asset references after a deploy. ETag and Last-Modified allow cheap revalidation with 304 Not Modified responses. Wrong headers cause either stale apps or needless re-downloads.
113

Collect Core Web Vitals from real users with the web-vitals library.

Save to ▾
Show answer
Report metrics to your analytics endpoint: import { onLCP, onINP, onCLS } from 'web-vitals'; function send(metric) { const body = JSON.stringify({ name: metric.name, value: metric.value, rating: metric.rating, page: location.pathname }); navigator.sendBeacon('/analytics/vitals', body); } onLCP(send); onINP(send); onCLS(send); sendBeacon delivers data reliably even when the page is closing. Aggregate at the 75th percentile per page.
114

Configure cache headers for hashed assets and HTML in Nginx.

Save to ▾
Show answer
Long cache for fingerprinted files, revalidation for HTML: location ~* \.(js|css|woff2|avif|webp|svg)$ { add_header Cache-Control 'public, max-age=31536000, immutable'; } location / { try_files $uri /index.html; add_header Cache-Control 'no-cache'; } This works only when build tools add content hashes to filenames. Returning users then load assets from cache instantly, while every deploy still reaches them through fresh HTML.
115

The main JavaScript bundle is 2 MB. How do you reduce it?

Save to ▾
Show answer
I would run a bundle analyser to see what is inside. Usual wins are route-based code splitting, lazy loading heavy widgets, replacing large libraries such as full date or utility packages with smaller alternatives or native APIs, and importing only needed functions so tree shaking works. I would also remove polyfills for browsers we no longer support, and make sure production mode and minification are enabled.
116

Marketing has added many third-party scripts and the site has become slow. What do you do?

Save to ▾
Show answer
I would measure each script's cost using the Performance panel and request blocking in DevTools. Then I would remove unused tags, load the rest with async or defer, delay non-essential ones like chat widgets until user interaction or idle time, and use facades, such as a static image for embedded videos. Moving tag management to a server-side setup and agreeing on a script budget with marketing prevents repeat problems.
117

Most of your users are on low-end Android phones with slow networks. How does that change your approach?

Save to ▾
Show answer
I would test with real budget devices and network throttling rather than a fast laptop. The priorities become sending less JavaScript, rendering important content on the server, compressing images aggressively, and keeping the initial page under a strict size budget. Caching with a service worker helps repeat visits. Features like large animations and heavy carousels should be reduced or loaded only when the device can handle them.
118

An audit finds twenty performance issues. How do you decide what to fix first?

Save to ▾
Show answer
Situation: a Lighthouse and field data audit of our app listed many issues. Task: I had one sprint to show improvement. Action: I ranked issues by user impact and effort, focusing first on the LCP image, render-blocking CSS and one oversized library on the most visited pages. Result: these three fixes delivered most of the gain, and I scheduled the remaining smaller items into later sprints.
119

How do HTTP/2 and HTTP/3 change frontend bundling strategy?

Save to ▾
Show answer
HTTP/1.1 allowed few parallel requests per domain, so developers concatenated everything into one big bundle and used domain sharding. HTTP/2 multiplexes many requests over one connection, and HTTP/3 runs over QUIC to avoid head-of-line blocking on lossy mobile networks. This makes moderate chunk splitting efficient and improves caching, because changing one module invalidates only its chunk. Still, too many tiny files add overhead.
120

What is hydration and why can it hurt performance?

Save to ▾
Show answer
With server-side rendering, the server sends ready HTML so content appears quickly, but the page is not interactive until the framework downloads JavaScript and hydrates, attaching event listeners and rebuilding state. On slow phones, this causes a gap where buttons look ready but do nothing, hurting INP. Techniques like streaming, partial or progressive hydration, islands architecture and server components reduce how much JavaScript must hydrate.
121

Break a long-running loop into smaller tasks to keep the page responsive.

Save to ▾
Show answer
Yield to the main thread between chunks: function yieldToMain() { if (globalThis.scheduler?.yield) return scheduler.yield(); return new Promise(resolve => setTimeout(resolve, 0)); } async function processRows(rows) { let last = performance.now(); for (const row of rows) { processRow(row); if (performance.now() - last > 40) { await yieldToMain(); last = performance.now(); } } } The browser can handle clicks between chunks, which improves INP. scheduler.yield keeps the task's priority after resuming.
122

Enforce a performance budget in CI with Lighthouse CI.

Save to ▾
Show answer
Add a lighthouserc.json with assertions: { "ci": { "collect": { "url": ["http://localhost:4173/"], "numberOfRuns": 3 }, "assert": { "assertions": { "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }], "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }], "total-byte-weight": ["warn", { "maxNumericValue": 400000 }] } } } } Run npx lhci autorun after building. Pull requests that break the budget fail, so regressions are caught before release.
123

Field data shows poor INP on the product page's Add to Cart button. How do you investigate?

Save to ▾
Show answer
I would reproduce with CPU throttling in the Performance panel and record the click to see what runs. Typical findings are a heavy handler, analytics calls running synchronously, or a large re-render of the whole page. Fixes include updating the button state immediately, deferring non-urgent work like tracking with scheduler.yield or requestIdleCallback, and narrowing which components re-render. Attribution data from web-vitals helps find the exact element and script.
124

Lighthouse shows a score of 95, but Search Console reports failing Core Web Vitals. Why?

Save to ▾
Show answer
Lighthouse is a single lab run on a simulated device, while Search Console uses field data from real Chrome users at the 75th percentile over 28 days. Real users may have slower phones, interact with the page, which affects INP and CLS later in the session, or see different content like ads and personalised blocks. I would add real user monitoring with attribution to find which pages and elements fail.
125

How did you convince stakeholders to prioritise performance work over new features?

Save to ▾
Show answer
Situation: our product team kept postponing performance fixes for new features. Task: I needed to show the business value. Action: I correlated our real user data with conversion and found that sessions with LCP above 4 seconds converted at about half the rate. I proposed a two-sprint plan with clear targets. Result: leadership approved it, LCP improved by 40 percent and checkout conversion rose measurably.
126

What is a JavaScript bundler and why do frontend projects need one?

Save to ▾
Show answer
A bundler takes your source files, follows imports to build a dependency graph and outputs optimised files for the browser. Along the way it transpiles TypeScript and JSX, processes CSS, handles images, minifies code, removes unused exports and adds content hashes for caching. Native ES modules work in browsers, but loading hundreds of unoptimised files is slow, so production builds still use bundlers like Vite, webpack or Rspack.
127

Why is Vite faster than webpack during development?

Save to ▾
Show answer
webpack bundles the whole application before serving it, so startup and updates slow down as the project grows. Vite serves source files as native ES modules and transforms only the files the browser requests, while pre-bundling dependencies with esbuild. Hot updates touch only the changed module. For production, Vite uses Rollup, and newer versions use the Rust-based Rolldown, to create optimised bundles.
128

What are source maps and should they be deployed to production?

Save to ▾
Show answer
Source maps link minified production code back to the original source files and line numbers, so DevTools and error trackers show readable stack traces. They are generated by the bundler as .map files. Many teams upload them privately to monitoring tools like Sentry and do not serve them publicly, which keeps original source code less exposed while still allowing debugging of production errors.
129

Explain dependencies, devDependencies and peerDependencies.

Save to ▾
Show answer
dependencies are packages needed at runtime, such as a charting library. devDependencies are used only during development and build, like Vite, ESLint, TypeScript and test runners. peerDependencies are declared by libraries that expect the host project to provide a package, for example a React component library listing react as a peer, which avoids shipping two copies. For bundled frontend apps, the split mainly documents intent and affects installs.
130

What do caret and tilde mean in package versions, and why commit the lockfile?

Save to ▾
Show answer
Versions follow semantic versioning: major.minor.patch. ^1.4.2 allows any 1.x.x version at or above 1.4.2, and ~1.4.2 allows only patch updates within 1.4.x. Because ranges can resolve differently over time, the lockfile, such as package-lock.json or pnpm-lock.yaml, records exact installed versions. Committing it and using npm ci in CI ensures every developer and build uses identical dependencies.
131

Compare npm, pnpm and Yarn.

Save to ▾
Show answer
npm comes with Node and is the default. pnpm stores each package version once in a global content-addressable store and links it into projects, saving disk space and installing faster, and its strict node_modules layout prevents using undeclared dependencies. Yarn offers workspaces and Plug'n'Play mode. All three support workspaces for monorepos. pnpm has become very popular for large projects because of speed and strictness.
132

What is Hot Module Replacement?

Save to ▾
Show answer
Hot Module Replacement updates changed modules in the running app without a full page reload, so you keep current state such as an open modal or form input while editing code. The dev server watches files, sends the update over a WebSocket, and the framework plugin swaps the module. CSS updates apply instantly. If a change cannot be applied safely, the dev server falls back to a full reload.
133

Create a new Vite project with TypeScript and run it.

Save to ▾
Show answer
Use the official scaffolding tool: npm create vite@latest my-app -- --template vanilla-ts cd my-app npm install npm run dev # starts the dev server with HMR npm run build # type-checks and builds to dist/ npm run preview # serves the production build locally Templates also exist for react-ts, vue-ts and svelte-ts. The dist folder contains static files ready for any web server or CDN.
134

Tell me about a time you automated a repetitive task.

Save to ▾
Show answer
Situation: in my internship, we resized and compressed product images manually before every upload. Task: I wanted to save the team time. Action: I wrote a small Node script using Sharp that converted images to WebP in three sizes and added it as an npm script. Result: a task that took an hour per batch became a one-minute command, and image sizes dropped by more than half.
135

What is tree shaking and what can prevent it?

Save to ▾
Show answer
Tree shaking removes exports that are never imported from the final bundle. It relies on static ES module imports and exports. It fails with CommonJS modules, dynamic property access on imported objects, and code with side effects that the bundler must keep. Libraries mark themselves safe with sideEffects: false in package.json. Importing { debounce } from a modular package instead of the whole library lets tree shaking work.
136

What is transpilation and how does browserslist control it?

Save to ▾
Show answer
Transpilation converts modern JavaScript or TypeScript into syntax that target browsers understand. Tools include Babel, SWC and esbuild. A browserslist setting, such as '> 0.5%, last 2 versions, not dead', or a build target like es2020, decides which features need conversion and which polyfills to include. Targeting only modern browsers produces smaller, faster code, so the support list should be based on real analytics.
137

How do environment variables work in frontend builds, and what is the risk?

Save to ▾
Show answer
Build tools replace variables at build time, for example Vite exposes only variables prefixed with VITE_ through import.meta.env. The values become plain text inside the JavaScript bundle that anyone can read. So frontend environment variables must never hold secrets like private API keys or database passwords; those belong on a server or backend proxy. Use them only for public configuration such as API base URLs.
138

Why do build tools add content hashes to filenames?

Save to ▾
Show answer
A content hash, like main.8c1f2a.js, changes only when the file's contents change. This lets servers cache assets for a year without worrying about stale code, because a new deploy references new filenames from fresh HTML. Unchanged chunks keep their hashes, so users re-download only what changed. Splitting vendor code into its own chunk improves this, since libraries change less often than app code.
139

Configure a path alias and an API proxy in Vite.

Save to ▾
Show answer
vite.config.ts: import { defineConfig } from 'vite'; import { fileURLToPath, URL } from 'node:url'; export default defineConfig({ resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }); Imports like '@/utils/format' work anywhere, and the proxy avoids CORS issues in development. Add matching paths in tsconfig for editor support.
140

Set up ESLint with the flat config for a TypeScript project.

Save to ▾
Show answer
Install packages and create eslint.config.js: // npm i -D eslint @eslint/js typescript-eslint import js from '@eslint/js'; import tseslint from 'typescript-eslint'; export default tseslint.config( js.configs.recommended, ...tseslint.configs.recommended, { rules: { '@typescript-eslint/no-explicit-any': 'warn', 'no-console': ['warn', { allow: ['error'] }] } }, { ignores: ['dist/'] } ); Run npx eslint . locally and in CI. Leave formatting to Prettier so the two tools do not conflict.
141

Run linting and formatting automatically before each commit.

Save to ▾
Show answer
Use Husky with lint-staged: npm i -D husky lint-staged npx husky init echo 'npx lint-staged' > .husky/pre-commit // package.json "lint-staged": { "*.{ts,tsx,js}": ["eslint --fix", "prettier --write"], "*.{css,md,json}": "prettier --write" } Only staged files are checked, so commits stay fast. CI should still run the full lint because hooks can be skipped locally.
142

Write a GitHub Actions workflow that lints, tests and builds a frontend app.

Save to ▾
Show answer
A typical CI pipeline: name: CI on: [pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci - run: npm run lint - run: npm test -- --run - run: npm run build npm ci installs exactly from the lockfile, and caching speeds up repeat runs. Branch protection can require this check before merging.
143

The build works on your machine but fails in CI. How do you troubleshoot?

Save to ▾
Show answer
I would compare environments first: Node version, package manager version, and whether CI uses npm ci with the committed lockfile while I used npm install. Other common causes are case-sensitive file paths on Linux, missing environment variables, and files ignored by Git. Pinning Node with an .nvmrc or engines field and reproducing the CI command locally in a clean clone or container usually reveals the difference.
144

npm audit reports a critical vulnerability in a dependency. What do you do?

Save to ▾
Show answer
I would check whether the vulnerable package is actually used in the production bundle or only in build tooling, and whether the vulnerable code path is reachable. Then I would update to a patched version, using npm update or overrides for transitive dependencies. If no fix exists, I would look for an alternative package or mitigate. Automated tools like Dependabot or Renovate keep dependencies current.
145

After a release, the app shows a blank page in some older browsers. What could be wrong?

Save to ▾
Show answer
The build probably targets newer syntax or uses an API that older browsers lack, causing a parse error that stops the whole bundle. I would check the console in that browser, compare the build target and browserslist with our support policy, and add missing polyfills for used APIs. Error monitoring with browser breakdowns catches this quickly, and testing on the oldest supported browser should be part of release checks.
146

How do monorepo tools like Turborepo or Nx help frontend teams?

Save to ▾
Show answer
A monorepo holds multiple apps and shared packages, such as a design system and utilities, in one repository using package manager workspaces. Turborepo and Nx add a task graph that understands dependencies between packages, run builds and tests only for affected packages, and cache outputs locally and remotely. This keeps CI fast as the repo grows and makes cross-package changes atomic in one pull request.
147

Split vendor libraries into separate chunks and visualise the bundle.

Save to ▾
Show answer
Use manualChunks and a visualiser plugin: import { defineConfig } from 'vite'; import { visualizer } from 'rollup-plugin-visualizer'; export default defineConfig({ plugins: [visualizer({ filename: 'stats.html', gzipSize: true })], build: { rollupOptions: { output: { manualChunks: { charts: ['chart.js'], editor: ['@tiptap/core'] } } } } }); After building, open stats.html to see which modules take the most space and decide what to lazy load or replace.
148

Production builds take eight minutes on an old webpack setup. How would you speed them up?

Save to ▾
Show answer
I would profile the build to find slow loaders and plugins. Quick wins include replacing Babel and Terser with SWC or esbuild, enabling persistent filesystem caching, and removing unused plugins. For a bigger gain, I would evaluate migrating to Vite or Rspack, which keeps webpack-compatible config. In CI, caching dependencies and build outputs and running only affected packages in a monorepo cut time further.
149

The bundle analyser shows two copies of the same library. How do you fix it?

Save to ▾
Show answer
Duplicates happen when different packages depend on incompatible versions, so the package manager installs both. I would run npm ls library-name or pnpm why to see who requires which version, then update dependents to a common version, use overrides or resolutions to force one compatible version, or configure resolve.dedupe in Vite. Duplicate framework copies are especially harmful because they can break shared state and context.
150

Describe a disagreement with your team about choosing a tool or framework.

Save to ▾
Show answer
Situation: half the team wanted a heavy state management library for a new app, while I felt it was unnecessary. Task: we needed a decision quickly. Action: I suggested building the same feature both ways in a time-boxed spike and compared code size, learning curve and bundle impact. Result: the data favoured the simpler approach, the team agreed, and we documented criteria for when to add the library later.
151

What is the testing pyramid, and how does the testing trophy differ?

Save to ▾
Show answer
The testing pyramid suggests many fast unit tests, fewer integration tests and very few slow end-to-end tests. The testing trophy, popular for frontend work, puts most effort into integration tests that render components with real behaviour, supported by static analysis like TypeScript and ESLint at the base. Both aim for confidence at reasonable cost; the trophy reflects that UI bugs often appear where units connect.
152

What is the difference between unit, integration and end-to-end tests?

Save to ▾
Show answer
A unit test checks a single function or module in isolation, such as a price formatter. An integration test checks several pieces working together, like a form component with validation and an API call mocked at the network level. An end-to-end test drives a real browser through a complete user flow, such as signup to dashboard, against a running app. They trade speed for realism in that order.
153

Write a Vitest unit test for an INR price formatter.

Save to ▾
Show answer
Test normal, edge and invalid inputs: // format.ts export const formatINR = (paise: number) => new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR' }).format(paise / 100); // format.test.ts import { describe, it, expect } from 'vitest'; import { formatINR } from './format'; describe('formatINR', () => { it('formats lakhs with Indian grouping', () => { expect(formatINR(12345600)).toBe('\u20b91,23,456.00'); }); it('handles zero', () => { expect(formatINR(0)).toBe('\u20b90.00'); }); });
154

A bug reached production even though all tests passed. What do you do next?

Save to ▾
Show answer
First I fix the bug and confirm the fix with the user's steps. Then I write a regression test that fails without the fix and passes with it, so the same bug cannot return. I also ask why existing tests missed it, for example mocks that hid the real API shape or a browser not covered, and improve that gap rather than only adding one test.
155

QA reports a bug that happens only in Safari on iPhone. How do you debug it?

Save to ▾
Show answer
I would reproduce it on a real iPhone or simulator and connect Safari Web Inspector from a Mac to see console errors and network requests. Common causes are unsupported APIs, date parsing differences, 100vh and viewport issues, and input zoom on small fonts. Cloud device platforms help if no device is available. After fixing it, I would add WebKit to the Playwright browser matrix to catch similar issues.
156

How do you test that a page shows the right message when an API fails?

Save to ▾
Show answer
I would simulate failures at the network layer with Mock Service Worker or Playwright's page.route, returning 500 responses, timeouts or malformed JSON. Then I assert that the user sees a clear error message, a retry button works, and no broken partial data appears. Testing empty states and slow responses with loading indicators is equally important, because these paths are often skipped in manual testing.
157

How do you handle a situation where a bug you wrote affected users?

Save to ▾
Show answer
Situation: a form validation change I made blocked some users from signing up with valid numbers. Task: I needed to fix it and be transparent. Action: I informed my lead immediately, rolled back the change, found that my regex rejected numbers with a +91 prefix, fixed it, and added tests for different formats. Result: signups recovered within an hour, and I now test with real-world data samples.
158

How do you work with QA engineers as a frontend developer?

Save to ▾
Show answer
I treat QA as partners rather than gatekeepers. I share feature details and edge cases early, give them testable builds on staging, and add stable test ids or accessible labels that help automation. When bugs come in, I ask for clear steps and environment details instead of arguing. In my last project, this collaboration cut bug reopen rates because we agreed on acceptance criteria before development.
159

Why are many teams moving from Jest to Vitest?

Save to ▾
Show answer
Vitest reuses the Vite configuration and transform pipeline, so TypeScript, JSX, ES modules and path aliases work without separate Babel or ts-jest setup. It is fast thanks to on-demand transformation and smart watch mode, and its API is largely compatible with Jest, making migration easy. Jest remains mature and widely used, especially in projects not built with Vite. Both support mocking, snapshots and coverage.
160

What is the guiding principle of Testing Library and why query by role?

Save to ▾
Show answer
Testing Library encourages testing the way users interact: find elements by what they see and hear, not by implementation details like class names or component internals. getByRole('button', { name: 'Submit' }) is preferred because it also verifies the element is accessible. Tests written this way survive refactors and catch real accessibility problems. Test ids are a last resort when no accessible query works.
161

When should you mock in frontend tests, and what is Mock Service Worker?

Save to ▾
Show answer
Mock things that are slow, nondeterministic or outside your control, such as network calls, timers and browser APIs, but avoid mocking your own modules heavily, which makes tests pass while the real integration breaks. Mock Service Worker intercepts requests at the network level in both browser and Node, so your real fetch code runs against defined handlers. The same handlers can power tests, Storybook and local development.
162

What are the pros and cons of snapshot testing?

Save to ▾
Show answer
Snapshot tests save a component's rendered output and fail when it changes, which quickly detects unintended changes with little effort. However, large snapshots are hard to review, so developers often update them blindly, and they break on harmless markup changes. They work best for small, stable outputs like serialised config or error messages. For UI behaviour, explicit assertions on visible text and roles are more meaningful.
163

What is visual regression testing?

Save to ▾
Show answer
Visual regression testing captures screenshots of pages or components and compares them pixel by pixel with approved baselines, flagging unexpected visual changes such as broken spacing or wrong colours. Tools include Playwright's toHaveScreenshot, Chromatic with Storybook and Percy. To avoid false failures, run in consistent containers, disable animations, and mask dynamic content like dates. It catches CSS bugs that functional tests miss.
164

Mock fetch in a Vitest test to check error handling.

Save to ▾
Show answer
Stub the global fetch and assert behaviour: import { it, expect, vi, afterEach } from 'vitest'; import { loadCourses } from './api'; afterEach(() => vi.unstubAllGlobals()); it('returns an empty list when the server fails', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue( new Response('Server error', { status: 500 }) )); const result = await loadCourses(); expect(result).toEqual([]); expect(fetch).toHaveBeenCalledWith('/api/courses'); }); For larger suites, MSW handlers are cleaner than stubbing fetch in every test.
165

Write a Playwright end-to-end test for a login flow.

Save to ▾
Show answer
Use role-based locators and web-first assertions: import { test, expect } from '@playwright/test'; test('user can log in and see dashboard', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('student@example.com'); await page.getByLabel('Password').fill('Secret@123'); await page.getByRole('button', { name: 'Log in' }).click(); await expect(page).toHaveURL(/dashboard/); await expect(page.getByRole('heading', { name: 'My courses' })).toBeVisible(); }); Assertions auto-wait, so no manual sleeps are needed. Save login state with storageState to reuse in other tests.
166

Test a DOM form with Testing Library and user-event.

Save to ▾
Show answer
Simulate real typing and clicks: import { screen } from '@testing-library/dom'; import userEvent from '@testing-library/user-event'; import { mountSignupForm } from './signup'; it('shows an error for an invalid mobile number', async () => { document.body.innerHTML = '<div id=app></div>'; mountSignupForm(document.getElementById('app')); const user = userEvent.setup(); await user.type(screen.getByLabelText('Mobile'), '12345'); await user.click(screen.getByRole('button', { name: 'Sign up' })); expect(screen.getByRole('alert')).toHaveTextContent('Enter a valid mobile number'); }); toHaveTextContent comes from @testing-library/jest-dom.
167

You must change a complex legacy module that has no tests. How do you proceed safely?

Save to ▾
Show answer
I would first write characterisation tests that capture the current behaviour, including odd cases, by calling the module with real inputs and recording outputs. With that safety net, I make small refactors to separate logic from DOM or network code, adding focused tests as I go. Then I implement the change. Small commits and feature flags let me release gradually and roll back quickly if something breaks.
168

Management requires 100 percent code coverage, but bugs still appear. How do you respond?

Save to ▾
Show answer
Coverage shows which lines ran, not whether the behaviour was verified, so tests can execute code without meaningful assertions. I would explain this with an example and propose a balanced target, perhaps 80 percent on business logic, combined with quality measures like mutation testing, critical user flows covered by end-to-end tests and tracking escaped defects. Effort is better spent on risky areas than on trivial getters.
169

How do you test code that depends on the current date or timers?

Save to ▾
Show answer
Real time makes tests slow and nondeterministic. I would use fake timers, such as vi.useFakeTimers() and vi.setSystemTime(new Date('2026-11-12T10:00:00+05:30')), then advance time with vi.advanceTimersByTime to test debounce, countdowns or expiry logic instantly. Restoring real timers after each test avoids leaking state. Designing functions to accept a clock or date parameter makes them even easier to test.
170

Tell me about a time you pushed back on releasing code without tests.

Save to ▾
Show answer
Situation: near a release deadline, a teammate suggested skipping tests for a new checkout coupon feature. Task: I felt the risk to payments was too high. Action: I proposed covering only the critical paths, valid, expired and invalid coupons, and wrote those tests myself in two hours. Result: one test caught a discount rounding bug before release, and the feature shipped on time.
171

What makes frontend tests flaky?

Save to ▾
Show answer
Flaky tests pass and fail without code changes. Common causes are fixed sleeps instead of waiting for conditions, race conditions with network responses, shared state between tests, reliance on real time or random data, animations, and test order dependencies. Unstable selectors and real third-party services also contribute. Fixes include auto-waiting assertions, isolated test data, mocked clocks, and running tests in parallel to expose hidden shared state.
172

Add automated accessibility checks to Playwright tests.

Save to ▾
Show answer
Use the axe-core integration: import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; test('home page has no serious accessibility violations', async ({ page }) => { await page.goto('/'); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa']) .analyze(); const serious = results.violations.filter(v => ['serious', 'critical'].includes(v.impact)); expect(serious).toEqual([]); }); Automated tools catch only part of accessibility issues, so keyboard and screen reader checks are still needed.
173

End-to-end tests fail randomly in CI about one run in five. How do you stabilise them?

Save to ▾
Show answer
I would collect traces, videos and screenshots of failures to see what actually happened. Usual fixes are replacing fixed waits with auto-waiting assertions, waiting for specific network responses, seeding isolated test data per test, mocking unstable third-party services, and disabling animations. I would quarantine persistently flaky tests with a tracking ticket rather than letting the team ignore red builds, and retry only as a temporary measure.
174

The frontend test suite takes 20 minutes in CI. How do you make it faster?

Save to ▾
Show answer
I would measure which tests are slowest. Options include running tests in parallel and sharding across CI machines, moving logic checks from end-to-end tests into faster integration tests, reusing logged-in state instead of logging in every time, and running only tests affected by changed files on pull requests with the full suite nightly. Replacing real network calls with MSW and caching dependencies also helps.
175

How have you built a testing culture in a frontend team?

Save to ▾
Show answer
Situation: my team had almost no automated tests and frequent regressions. Task: as tech lead, I wanted testing to become a habit, not a mandate. Action: I set up Vitest and Playwright with examples, ran two short workshops, required tests only for bug fixes at first, and celebrated bugs caught in CI. Result: within six months, most pull requests included tests and production regressions dropped significantly.
176

Compare SPA, MPA, server-side rendering and static site generation.

Save to ▾
Show answer
A single-page app loads once and renders views in the browser, giving app-like navigation but slower first load. A multi-page app renders each page on the server with full reloads. Server-side rendering generates HTML per request for fast content and SEO, then hydrates. Static site generation pre-builds HTML at build time and serves it from a CDN, which is fastest for content that changes rarely, like blogs.
177

How does client-side rendering affect SEO and social sharing?

Save to ▾
Show answer
With pure client-side rendering, the initial HTML is mostly empty and content appears after JavaScript runs. Google can render JavaScript, but it happens later and less reliably, while many social media link previewers and some search engines do not run JavaScript at all, so titles and images are missing. Server rendering or prerendering important pages ensures content and meta tags are in the initial HTML.
178

At a high level, how does React's virtual DOM and reconciliation work?

Save to ▾
Show answer
React components return a description of the UI as a tree of elements. When state changes, React renders a new tree and compares it with the previous one, a process called reconciliation, then applies only the minimal DOM changes needed. Keys help it match list items correctly. The React Compiler now adds automatic memoisation, reducing unnecessary re-renders without manual useMemo calls.
179

Where do you see yourself in five years as a frontend developer?

Save to ▾
Show answer
In five years, I want to be a senior frontend engineer who owns complex features end to end and helps shape architecture decisions. I plan to deepen my skills in performance, accessibility and TypeScript, and contribute to a design system. I also want to mentor juniors, because teaching others is how I learned best. I hope to grow within a team where I can take on that responsibility.
180

What is the difference between local UI state, global state and server state?

Save to ▾
Show answer
Local state belongs to one component, like whether a dropdown is open. Global client state is shared across the app, like theme or the logged-in user. Server state is data owned by the backend, such as orders, that must be fetched, cached, refreshed and kept in sync. Treating server state separately with tools like TanStack Query removes much of the code teams once wrote in global stores.
181

What does component-driven development mean?

Save to ▾
Show answer
Component-driven development builds UIs from the bottom up as small, reusable, isolated components, such as buttons and inputs, composed into larger patterns like forms and pages. Each component has a clear API through props or attributes and is developed and documented in isolation, often in Storybook. Composition over configuration keeps components flexible. This approach enables design systems, parallel work and easier testing.
182

Write a minimal observable store in plain JavaScript.

Save to ▾
Show answer
A store holds state and notifies subscribers: function createStore(initial) { let state = initial; const listeners = new Set(); return { get: () => state, set(update) { state = { ...state, ...update }; listeners.forEach(fn => fn(state)); }, subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); } }; } const cart = createStore({ items: 0 }); const stop = cart.subscribe(s => (badge.textContent = s.items)); cart.set({ items: 2 }); The returned unsubscribe function prevents memory leaks.
183

Implement simple feature flags on the frontend.

Save to ▾
Show answer
Load flags once and check them where needed: let flags = {}; export async function loadFlags(userId) { const res = await fetch('/api/flags?user=' + encodeURIComponent(userId)); flags = res.ok ? await res.json() : {}; } export const isEnabled = (name) => flags[name] === true; // usage await loadFlags(currentUser.id); if (isEnabled('new-checkout')) renderNewCheckout(); else renderOldCheckout(); Defaulting to off when the request fails keeps the stable path. Flags enable gradual rollouts, but old flags must be removed to avoid clutter.
184

Write a React custom hook that fetches data and cancels on unmount.

Save to ▾
Show answer
Use AbortController inside useEffect: import { useEffect, useState } from 'react'; export function useFetch(url) { const [state, setState] = useState({ data: null, error: null, loading: true }); useEffect(() => { const controller = new AbortController(); setState(s => ({ ...s, loading: true })); fetch(url, { signal: controller.signal }) .then(r => (r.ok ? r.json() : Promise.reject(new Error(r.status)))) .then(data => setState({ data, error: null, loading: false })) .catch(err => { if (err.name !== 'AbortError') setState({ data: null, error: err, loading: false }); }); return () => controller.abort(); }, [url]); return state; } In production apps, TanStack Query adds caching and retries.
185

State is passed through many component layers and global state has become chaotic. How do you restructure it?

Save to ▾
Show answer
I would first classify state: server data moves to a data-fetching cache like TanStack Query, URL-worthy state such as filters moves to query parameters, and truly local state moves back into components. What remains global, like user session and theme, goes into a small, well-defined store or context split by concern. Composition patterns, passing components as children, also remove much prop drilling.
186

Your app needs to support Hindi and English. How do you design internationalisation?

Save to ▾
Show answer
I would move all UI text into translation files keyed by message ids, use an i18n library that supports plurals and interpolation, and format numbers, dates and currency with Intl APIs for each locale, like en-IN and hi-IN. Layouts must handle longer text and Devanagari fonts with proper line height. The locale goes in the URL, such as /hi/, with hreflang tags for SEO and lang on html.
187

A dashboard must show live order updates. Would you use polling, Server-Sent Events or WebSockets?

Save to ▾
Show answer
Polling is simplest and fine if updates every 30 seconds are acceptable. Server-Sent Events push one-way updates from server to browser over HTTP with automatic reconnection, ideal for live orders, notifications and stock tickers. WebSockets provide full two-way communication, needed for chat or collaborative editing, but require more infrastructure. For a one-way dashboard, I would choose SSE and reconnect gracefully on network changes.
188

How would you choose a frontend framework for a new project?

Save to ▾
Show answer
I would base it on project needs and team context rather than trends: team experience, hiring market in India, SEO and rendering requirements, ecosystem maturity, long-term support and performance on target devices. React has the largest ecosystem and talent pool, Angular suits large enterprise teams wanting an opinionated structure, Vue is approachable, and Svelte or Astro produce less JavaScript. A small proof of concept helps confirm the choice.
189

Users report errors you cannot reproduce. How do you set up frontend observability?

Save to ▾
Show answer
I would add an error monitoring tool like Sentry with uploaded source maps, release tags and user context such as browser and route, without personal data. Real user monitoring tracks Web Vitals and slow API calls. Breadcrumbs of clicks and network requests before an error help reproduce issues, and session replay with privacy masking shows what users saw. Alerts on error spikes after a release enable quick rollbacks.
190

How do you estimate the time needed for a frontend feature?

Save to ▾
Show answer
I break the feature into small parts: components, states such as loading, empty and error, API integration, responsive behaviour, accessibility and tests. I estimate each part, add time for reviews and unknowns, and flag dependencies like pending designs or APIs. For example, a filter panel I estimated at five days was delivered in five because I had listed edge cases upfront instead of discovering them late.
191

How do you balance technical debt against delivering new features?

Save to ▾
Show answer
Situation: our team constantly added features while an outdated build setup slowed everyone. Task: I needed time for cleanup without stalling the roadmap. Action: I quantified the cost, about 30 minutes lost per developer daily, and proposed reserving roughly 15 percent of each sprint for debt, starting with the biggest pain points. Result: the product manager agreed, build times halved, and feature delivery actually sped up.
192

What are micro-frontends and when do they make sense?

Save to ▾
Show answer
Micro-frontends split a large web app into independently developed and deployed pieces, each owned by a team, composed at runtime with Module Federation, web components or server-side composition. They help large organisations where many teams block each other on one release. The costs are duplicated dependencies, inconsistent UX, complex shared state and harder performance tuning. For most teams, a well-structured monorepo is simpler.
193

Where should a frontend store authentication tokens?

Save to ▾
Show answer
Tokens in localStorage are readable by any script, so a single XSS exposes them. The safer pattern is an HttpOnly, Secure, SameSite cookie set by the server, invisible to JavaScript, paired with CSRF protection. When an access token must be held in JavaScript, keep it short-lived in memory and refresh it using an HttpOnly refresh cookie. A backend-for-frontend layer can keep tokens entirely off the browser.
194

Build an API client that refreshes an expired access token once and retries.

Save to ▾
Show answer
Share one refresh promise so parallel requests do not refresh repeatedly: let refreshing = null; async function api(path, options = {}) { let res = await fetch(path, { ...options, credentials: 'include' }); if (res.status !== 401) return res; refreshing ??= fetch('/auth/refresh', { method: 'POST', credentials: 'include' }) .finally(() => (refreshing = null)); const refreshed = await refreshing; if (!refreshed.ok) { location.assign('/login'); throw new Error('Session expired'); } return fetch(path, { ...options, credentials: 'include' }); } Cookies carry the tokens, so JavaScript never reads them.
195

Which rendering strategy would you choose for a large e-commerce site?

Save to ▾
Show answer
I would mix strategies by page type. Category and product pages need SEO and fast LCP, so I would use static generation with incremental revalidation or server rendering with CDN caching. Cart, checkout and account pages are personalised, so server rendering without shared caching or client rendering works. Prices and stock can be fetched fresh on the client. Frameworks like Next.js, Nuxt and Astro support this per-route choice.
196

Ten teams work on one large frontend and releases keep blocking each other. What would you propose?

Save to ▾
Show answer
I would first try a modular monorepo with clear package boundaries, code owners, affected-only CI with Nx or Turborepo, and trunk-based development with feature flags, which removes most blocking at low cost. If teams truly need independent deployments, I would introduce micro-frontends along business domains, such as search, product and checkout, with a shared design system and strict performance budgets to control duplication.
197

How do you secure a modern frontend application beyond what the framework provides?

Save to ▾
Show answer
Frameworks escape output by default, but risks remain: dangerouslySetInnerHTML or v-html with unsanitised data, javascript: URLs in links, and vulnerable npm packages. I would sanitise any HTML with DOMPurify, enforce a strict Content Security Policy with nonces, use Subresource Integrity for CDN scripts, keep tokens in HttpOnly cookies, run dependency audits in CI and never place secrets in frontend environment variables.
198

Design the frontend for a search box with autocomplete suggestions.

Save to ▾
Show answer
I would debounce input by about 200 milliseconds, cancel outdated requests with AbortController and cache recent queries in a Map. Suggestions render in an accessible combobox pattern with aria-expanded, aria-activedescendant and arrow key navigation. I would highlight matched text safely, show loading and empty states, and limit results to around eight. Measuring latency and prefetching popular terms keep it feeling instant on slow networks.
199

Field agents need a form app that works in areas with no internet. How would you build it?

Save to ▾
Show answer
I would build an offline-first PWA: a service worker caches the app shell, and submissions are saved in IndexedDB with a sync status. When connectivity returns, a background sync or online event uploads pending records with idempotency keys so retries never create duplicates. The UI clearly shows synced and pending items. Conflict rules, such as server timestamps winning, must be agreed with the backend team.
200

Tell me about a frontend architecture decision you led.

Save to ▾
Show answer
Situation: our app's single global store had grown to thousands of lines and slowed every feature. Task: as frontend lead, I had to propose a better structure without stopping delivery. Action: I wrote a short design document comparing options, moved server data to a query cache module by module, and set guidelines for local state. Result: the store shrank by 70 percent and new features took noticeably less time.

Frequently asked questions

What jobs can I get after this frontend development course?

You can apply for Frontend Developer, UI Engineer, JavaScript Developer and TypeScript Frontend Engineer roles, and with experience move into frontend performance, test automation or frontend architect positions. Product startups, fintech and e-commerce companies, SaaS firms and global capability centres in India actively hire frontend engineers. Recruiters look for strong JavaScript fundamentals, TypeScript, performance awareness and well-built projects you can explain in depth.

Who is eligible for this frontend development course?

The course suits learners who already know basic HTML, CSS and JavaScript, such as final-year students, recent graduates from any stream, web developers wanting to specialise, and backend or QA engineers moving to frontend work. If you are a complete beginner, start with a web development fundamentals course first. Comfort with building small web pages and using Git will help you get the most from the advanced topics.

How long does it take to become job-ready in frontend development?

With basic web skills and two to three hours of daily practice, most learners become job-ready for frontend roles in about three to four months, covering modern JavaScript, TypeScript, browser APIs, performance, tooling and testing. Mastering architecture and large-scale performance takes longer and grows with real project experience. Building and deploying projects every week is the fastest way to turn concepts into interview-ready skills.

Do I need to learn React for frontend jobs?

Most frontend job listings in India mention a framework, and React is the most common, so learning one is important. However, interviews at good companies test core JavaScript, TypeScript, browser behaviour, CSS and performance deeply, because frameworks change but fundamentals do not. This course builds those fundamentals and touches on framework concepts; pairing it with a dedicated React course gives you the complete skill set employers expect.

Will I get a certificate, and does it help in hiring?

You receive a course completion certificate that shows structured training in advanced frontend topics, which can help during resume screening. Hiring decisions, though, depend mostly on your technical interviews and portfolio. Combine the certificate with deployed projects that show TypeScript, good Lighthouse scores, tests and clean Git history. A well-documented GitHub profile often matters more to frontend hiring managers than any certificate.

What projects should I build to impress frontend interviewers?

Choose projects that show depth rather than many small clones: an offline-capable PWA, a typed storefront with server-rendered product pages, a reusable component library with Storybook and tests, and a performance case study where you measured and improved Core Web Vitals. Document decisions and results in the README. Interviewers often ask why you chose a pattern, so be ready to explain trade-offs.