Skip to content

Frontend Development Technical Round Questions & Answers

40 questions for the technical round stage — 3 easy, 26 medium, 11 hard.

About the technical round round

The technical round is the core of a Frontend Development interview: concept questions with follow-ups, a live hands-on task and a deep dive into one of your projects. Interviewers check depth, not just definitions.

Tips for the technical round round

  • Say your approach out loud before you start writing.
  • After a correct answer, expect 'why?' and 'what if?' follow-ups; go one level deeper than the question.
  • Pick one project you can explain end to end, including what you would do differently.
  • If you get stuck, explain what you would try next instead of going silent.
Solved — technical roundStart 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.

Frequently asked questions

What happens in the Frontend Development technical round round?

The technical round is the core of a Frontend Development interview: concept questions with follow-ups, a live hands-on task and a deep dive into one of your projects. Interviewers check depth, not just definitions.

How do I clear the technical round round?

Say your approach out loud before you start writing. After a correct answer, expect 'why?' and 'what if?' follow-ups; go one level deeper than the question. Pick one project you can explain end to end, including what you would do differently.

Which round comes next?

A typical order is online assessment, telephonic screening, one or two technical rounds, a managerial round and HR. Use the other round pages to practise each stage.