Frontend technical interviews have shifted dramatically in 2026. Because AI assistants can instantly generate standard code challenges, interviewers have moved beyond basic definitions. They now test depth of understanding — browser execution, React 19 internals, AI token streaming, performance profiling, and architectural decision-making under real constraints.
This guide covers every round you'll face, with actual questions and answers from real interviews at top Indian and global product companies.
Cracking the Frontend Interview in 2026 — Full Breakdown
What to expect in each round
| Round | What's Tested | Duration |
|---|---|---|
| Screening | DSA basics, JS fundamentals | 45–60 min |
| Technical Round 1 | React deep-dive, browser APIs | 60–90 min |
| Technical Round 2 | System design, architecture | 60–90 min |
| Hiring Manager | Past decisions, trade-offs | 45 min |
| Culture Fit | Communication, collaboration | 30 min |
JavaScript Fundamentals
Q1: What is the difference between == and === in JavaScript?
== performs type coercion before comparing. === compares both value and type with no coercion.
0 == false // true (false coerced to 0)
0 === false // false (number !== boolean)
'' == false // true
'' === false // false
null == undefined // true (special case)
null === undefined // false
Always use ===. The only acceptable use of == is checking for both null and undefined simultaneously: value == null.
Q2: Explain closures — and how they can cause memory leaks in SPAs.
A closure is a function that retains access to its lexical scope even after the outer function has returned.
function makeCounter() {
let count = 0; // captured in closure
return () => ++count;
}
const inc = makeCounter();
inc(); // 1
inc(); // 2
Memory leak scenario in React:
useEffect(() => {
const handler = () => {
// This handler captures a reference to a large data object
console.log(expensiveData);
};
window.addEventListener('resize', handler);
// BUG: no cleanup — handler and expensiveData leak after unmount
}, [expensiveData]);
Fix: Always return a cleanup function from useEffect.
useEffect(() => {
const handler = () => console.log(expensiveData);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler); // ✅
}, [expensiveData]);
Q3: What is the event loop, and what's the difference between microtasks and macrotasks?
The event loop processes the call stack, then microtask queue, then macrotask queue — in that exact order per iteration.
| Examples | When runs | |
|---|---|---|
| Microtasks | Promise.then, queueMicrotask, MutationObserver | After each task, before rendering |
| Macrotasks | setTimeout, setInterval, setImmediate, I/O | One per event loop tick |
console.log('1');
setTimeout(() => console.log('2'), 0); // macrotask
Promise.resolve().then(() => console.log('3')); // microtask
console.log('4');
// Output: 1, 4, 3, 2
Interviewers test this to see if you understand why Promise.then runs before setTimeout(fn, 0) even though both are async.
Q4: What is the difference between var, let, and const?
var | let | const | |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisted | Yes (as undefined) | Yes (TDZ error) | Yes (TDZ error) |
| Re-assignable | Yes | Yes | No |
| Re-declarable | Yes | No | No |
Temporal Dead Zone (TDZ): Accessing let or const before their declaration throws a ReferenceError. The variable exists in the scope but is in a "dead zone" until the declaration line is reached.
React Deep Dive
Q5: Why does my memoized component still re-render?
The most common interview filter. If you pass a new object or function reference as a prop every render, React.memo's shallow comparison will fail.
// BUG: new object on every render defeats memo
function Parent() {
return <MemoChild style={{ color: 'red' }} />;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// New reference each render
}
// FIX: stable reference with useMemo
function Parent() {
const style = useMemo(() => ({ color: 'red' }), []);
return <MemoChild style={style} />;
}
In React 19, the React Compiler handles many of these cases automatically. But interviewers still test this because the compiler doesn't handle every edge case, and you need to understand why it works.
Q6: When should you NOT use useMemo?
Memoization costs CPU. It adds overhead for dependency tracking and comparison. For cheap operations — sorting 10 items, string concatenation, simple math — recalculating is faster than memoizing.
Use useMemo when:
- The computation is expensive (e.g., filtering 10,000 items, complex math)
- The result is a dependency in another
useEffectoruseMemo - You're passing the result to a memoized child component
Skip useMemo when:
- The calculation is simple
- The component isn't a performance bottleneck (profile first!)
- You're doing premature optimization
Q7: What is the difference between Server Components and Client Components in Next.js?
This is the most tested Next.js question in 2025–2026.
| Server Component | Client Component | |
|---|---|---|
| Renders on | Server | Client (+ SSR) |
| Ships JS? | No | Yes |
| Can use hooks? | No | Yes |
| Can access DB? | Yes | No (directly) |
| Directive | Default | 'use client' |
Rules:
- Server Components can import and render Client Components
- Client Components cannot import Server Components
- The boundary should be as low in the tree as possible
// ✅ Correct: Client component wraps only the interactive part
// page.tsx (Server)
import { getJobs } from '@/lib/queries';
import JobList from './JobList'; // Server Component
export default async function Page() {
const jobs = await getJobs();
return <JobList jobs={jobs} />; // No 'use client' needed
}
// JobList.tsx (Server) — renders the list
import ApplyButton from './ApplyButton'; // Client Component for just the button
export function JobList({ jobs }) {
return jobs.map(job => (
<div key={job.id}>
<h2>{job.title}</h2>
<ApplyButton jobId={job.id} /> {/* ← client boundary pushed low */}
</div>
));
}
Q8: Explain useTransition and useDeferredValue.
Both mark updates as non-urgent so React can prioritize more important updates (like user input) first.
// useTransition: mark a state update as deferrable
const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleChange(e) {
setQuery(e.target.value); // urgent — update input immediately
startTransition(() => {
setResults(search(e.target.value)); // non-urgent — can wait
});
}
// useDeferredValue: defer a derived value
const deferredQuery = useDeferredValue(query);
const results = useMemo(() => search(deferredQuery), [deferredQuery]);
Use for: search inputs, filter panels, tab switching — any UI where typing should stay responsive while a heavy re-render catches up.
Q9: What are React 19 Actions and useActionState?
Actions are functions that handle async data mutations — form submissions, server updates. useActionState wraps them and provides pending state, error handling, and optimistic updates automatically.
'use client';
import { useActionState } from 'react';
async function submitForm(prevState: unknown, formData: FormData) {
const name = formData.get('name');
const res = await fetch('/api/profile', {
method: 'POST',
body: JSON.stringify({ name }),
});
if (!res.ok) return { error: 'Failed to save' };
return { success: true };
}
export function ProfileForm() {
const [state, action, isPending] = useActionState(submitForm, null);
return (
<form action={action}>
<input name="name" />
<button type="submit" disabled={isPending}>
{isPending ? 'Saving…' : 'Save'}
</button>
{state?.error && <p className="text-red-400">{state.error}</p>}
</form>
);
}
Q10: What is hydration mismatch and how do you fix it?
Hydration is when React attaches event listeners to server-rendered HTML. A mismatch happens when the HTML React generates on the client differs from what the server sent.
Common causes:
Date.now()orMath.random()in renderwindowornavigatoraccess in initial render- Invalid HTML nesting (
<p>inside<p>) - Browser extensions modifying the DOM
Fixes:
// ❌ Causes hydration mismatch
function Component() {
return <p>Current time: {Date.now()}</p>;
}
// ✅ Fix: move to useEffect (client-only)
function Component() {
const [time, setTime] = useState<number | null>(null);
useEffect(() => setTime(Date.now()), []);
return <p>Current time: {time ?? 'Loading…'}</p>;
}
Core Web Vitals & Performance
Important
Every senior frontend interview in 2026 includes at least one Core Web Vitals question. INP replaced FID as a Core Web Vital in March 2024, and many candidates are still answering with outdated information.
Q11: Explain LCP, INP, and CLS.
| Metric | Stands for | Good threshold | What it measures |
|---|---|---|---|
| LCP | Largest Contentful Paint | ≤ 2.5s | When the main content loads |
| INP | Interaction to Next Paint | ≤ 200ms | Responsiveness to user input |
| CLS | Cumulative Layout Shift | ≤ 0.1 | Visual stability |
How to improve LCP:
- Preload the hero image:
<link rel="preload" as="image" href="/hero.webp"> - Use
fetchpriority="high"on the LCP image - Move critical CSS inline
- Reduce server response time (TTFB)
How to improve INP:
- Break long tasks into smaller chunks using
scheduler.yield()(new API) orsetTimeout - Use
startTransitionfor non-urgent updates - Avoid blocking the main thread during animations
How to improve CLS:
- Always set explicit
widthandheighton images - Reserve space for ads and dynamic content with
min-height - Avoid inserting DOM above existing content after load
Q12: What is the difference between SSR, SSG, and ISR?
| Strategy | Renders | Best for | Trade-offs |
|---|---|---|---|
| SSR (Server-Side Rendering) | On every request | Dynamic, personalized pages | Slower TTFB than static |
| SSG (Static Site Generation) | At build time | Blogs, docs, marketing | Stale data until rebuild |
| ISR (Incremental Static Regeneration) | At build + on-demand revalidation | Job boards, e-commerce | Complex cache logic |
| CSR (Client-Side Rendering) | In browser | Dashboards, auth-gated UIs | Poor SEO, slower LCP |
In Next.js, ISR is implemented with revalidate:
// Revalidate every 60 seconds
export const revalidate = 60;
// Or on-demand in an API route:
import { revalidatePath } from 'next/cache';
revalidatePath('/jobs');
Frontend System Design
Q13: Design a real-time AI chat UI
This is asked at senior level at companies like Razorpay, Flipkart, and funded startups. Walk through each layer:
1. Streaming Architecture
User → React Client → Next.js API Route → LLM API (Gemini/OpenAI)
↑ Server-Sent Events (SSE) ←←←←←←←←←←←←←←
2. Client implementation (Vercel AI SDK)
'use client';
import { useChat } from 'ai/react';
export function ChatUI() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat',
});
return (
<div>
{messages.map(m => (
<div key={m.id} className={m.role === 'user' ? 'text-right' : ''}>
{m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button type="submit" disabled={isLoading}>Send</button>
</form>
</div>
);
}
3. Key design decisions to discuss:
- Token batching: don't
setStateon every token — batch every 50ms - Error boundaries for model failures / rate limits
- Optimistic UI: show user message immediately before API response
- Message streaming to
reffirst, then commit to state on completion for better INP
Q14: Design a virtualized infinite scroll list
For a job board with 10,000+ listings.
Key concepts:
- Only render DOM nodes for visible items (typically 10–20)
- Use
IntersectionObserverto trigger next page fetch react-windowor@tanstack/virtualfor virtualization
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualJobList({ jobs }: { jobs: Job[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: jobs.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 120, // estimated row height
});
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div style={{ height: rowVirtualizer.getTotalSize() }}>
{rowVirtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.index}
style={{ transform: `translateY(${virtualRow.start}px)` }}
>
<JobCard job={jobs[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
AI-Native Integration (2026 Special)
Q15: How do you protect LLM API keys in a React app?
Never expose API keys in client code. Route all LLM requests through a server-side layer.
// ❌ Never: exposes key in client bundle
const res = await fetch('https://api.openai.com/v1/...', {
headers: { Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_KEY}` }
});
// ✅ Always: BFF pattern — client calls your API route, not the LLM directly
// app/api/chat/route.ts
import { streamText } from 'ai';
import { google } from '@ai-sdk/google';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: google('gemini-2.0-flash'),
messages,
});
return result.toDataStreamResponse();
}
Q16: How do you prevent INP degradation when streaming tokens?
Streaming 1–5 tokens per chunk at 20+ chunks per second can cause heavy re-renders that block the main thread.
Strategies:
// Strategy 1: Throttle state updates
const THROTTLE_MS = 50;
let buffered = '';
let timer: ReturnType<typeof setTimeout> | null = null;
function onChunk(token: string) {
buffered += token;
if (!timer) {
timer = setTimeout(() => {
setContent(c => c + buffered);
buffered = '';
timer = null;
}, THROTTLE_MS);
}
}
// Strategy 2: Write directly to DOM via ref, commit to state on completion
function StreamingMessage() {
const ref = useRef<HTMLSpanElement>(null);
function onToken(token: string) {
if (ref.current) ref.current.textContent += token; // no re-render!
}
function onComplete(fullText: string) {
setMessage(fullText); // single re-render at the end
}
return <span ref={ref} />;
}
Accessibility (a11y)
Q17: What is ARIA and when should you use it?
ARIA (Accessible Rich Internet Applications) attributes convey semantic meaning to screen readers when native HTML isn't sufficient.
Rule 1: Use native HTML first
<!-- ❌ Bad: custom button with ARIA -->
<div role="button" tabindex="0" aria-pressed="false" onclick="...">Click me</div>
<!-- ✅ Good: native button — keyboard, focus, and semantics built-in -->
<button type="button">Click me</button>
When ARIA is appropriate: custom widgets (comboboxes, carousels, tabs) where there's no HTML equivalent.
<div role="tablist" aria-label="Job categories">
<button role="tab" aria-selected="true" aria-controls="remote-panel">Remote</button>
<button role="tab" aria-selected="false" aria-controls="onsite-panel">Onsite</button>
</div>
Testing
Q18: What separates a good component test from a bad one?
Good tests test behavior — what the user sees and does. Bad tests test implementation — how the code works internally.
// ❌ Bad: tests implementation details
expect(onSubmit).toHaveBeenCalled();
expect(setIsLoading).toHaveBeenCalledWith(true);
// ✅ Good: tests what the user sees
// Submit button disables during loading
const submitBtn = screen.getByRole('button', { name: /submit/i });
userEvent.click(submitBtn);
expect(submitBtn).toBeDisabled();
await screen.findByText(/success/i);
Prefer userEvent over fireEvent — userEvent simulates real browser events (pointerdown + mousedown + click + mouseup + pointerup), while fireEvent only fires a single event.
What actual companies pay
| Level | india |
|---|---|
| Junior (0-2y) | ₹15L – ₹25L |
| Mid (2-5y) | ₹25L – ₹40L |
| Senior (5-8y) | ₹40L – ₹55L |
| Staff (8y+) | ₹55L – ₹90L |
| Level | india |
|---|---|
| Junior (0-2y) | ₹10L – ₹18L |
| Mid (2-5y) | ₹18L – ₹27L |
| Senior (5-8y) | ₹27L – ₹45L |
| Staff (8y+) | ₹45L – ₹65L |
Real interview formats at top companies
| Company | Rounds | Focus |
|---|---|---|
| Swiggy / Zomato | 4 rounds | JS fundamentals, React, System Design, Culture |
| Razorpay | 5 rounds | DSA, React deep-dive, System Design, Leadership, HR |
| Dream11 | 4 rounds | React internals, Performance, System Design |
| Flipkart | 5 rounds | DSA, React, Micro-frontends, System Design, Bar Raiser |
| Zepto | 3 rounds | React, Full-stack awareness, Culture |
| Remote US startups | 3–4 rounds | Practical coding, Architecture, System Design |
Tip
At product companies, system design rounds weigh more than DSA. Prepare to whiteboard a real-time feed, infinite scroll list, or AI chat interface. Those questions appear in almost every senior loop in 2026.
How to prepare in 4 weeks
Week 1 — JavaScript fundamentals
- Closures, event loop, prototypes,
thisbinding - ES2023+ features:
Array.at(),Object.groupBy(),Promise.allvsallSettled
Week 2 — React deep-dive
- Re-render optimization,
useTransition,useDeferredValue - React 19: Actions,
useActionState, the React Compiler - Server Components architecture
Week 3 — Performance & browser
- Core Web Vitals (LCP, INP, CLS)
- Network waterfall analysis
- CSS rendering performance
Week 4 — System design
- AI chat streaming UI
- Virtualized list with infinite scroll
- Design system architecture
FAQ
Are PropTypes still used in React 19?
No. PropTypes have been officially deprecated. TypeScript is the industry standard for component typing in all professional codebases.
What is the most-tested React concept?
Hooks — specifically stale closures in useEffect, incorrect dependency arrays, and referential equality breaking React.memo.
What is the difference between hydration and mounting?
Mounting is when React builds the DOM tree from scratch in the browser. Hydration is when React attaches event listeners and state to pre-rendered HTML sent from the server — it's faster because the DOM already exists.
How should I answer system design questions?
Start with requirements (scale, constraints), then propose a high-level architecture, then drill into the hardest technical challenge. Talk through trade-offs — there's rarely one right answer, and interviewers want to see how you think.
Related reading
- Browse frontend developer jobs
- Frontend developer salary in India 2026
- Most in-demand frontend skills in 2026
- React interview questions that actually matter
- Frontend system design interview guide
- Salary calculator
