Skip to content

Hard Frontend Development Interview Questions

45 hard questions with answers. Design, trade-off and deep-dive questions asked for senior roles and at top product companies.

Topics in the hard set

Frontend Architecture: 9 Web Performance: 7 TypeScript: 6 Browser APIs: 5 Build Tools and Tooling: 5 Frontend Testing: 5 Modern JavaScript: 4 CSS Architecture and Accessibility: 4
Solved — hardStart 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 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.
02

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.
03

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.
04

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.
05

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.
06

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.
07

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.
08

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.
09

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.
10

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.
11

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.
12

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.
13

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.
14

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.
15

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.
16

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.
17

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.
18

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.
19

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.
20

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.
21

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.
22

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.
23

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.
24

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.
25

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.
26

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.
27

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.
28

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.
29

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.
30

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.
31

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.
32

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.
33

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.
34

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.
35

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.
36

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.
37

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.
38

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.
39

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.
40

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.
41

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.
42

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.
43

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.
44

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.
45

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

Who should practise hard Frontend Development questions?

Candidates targeting senior roles or top product companies, and anyone who finds the medium set easy.

How should I practise these?

Answer each question aloud before opening the answer, mark it as solved only when your answer matches the key idea, and save misses to Last-Minute Revision.

Other difficulty levels