Skip to content
Frontend 28 min read

Frontend Interview Guide 2026: HTML, CSS, JavaScript, React Complete Prep

Complete frontend interview preparation covering HTML, CSS, JavaScript, TypeScript, React, and system design.

By SDE Roadmap

Frontend Interview Landscape

Frontend interviews test HTML, CSS, JavaScript, and framework knowledge. The bar is rising with modern web complexity. Companies expect candidates to demonstrate not just syntax knowledge but deep understanding of how browsers render pages, how JavaScript executes asynchronously, and how to build performant, accessible user interfaces at scale.

This guide covers the most frequently asked questions across HTML, CSS, JavaScript, and React, with code examples, explanations, and practical tips to help you ace your next frontend interview.

HTML & Accessibility

Q1: Semantic HTML

Semantic HTML means using elements that describe the meaning of content, not just its appearance.

```html

\`\`\`

Why it matters:

  • Screen readers rely on semantic elements to convey structure to visually impaired users. A <nav> element announces itself as navigation, while a <div> does not.
  • SEO: Search engines give more weight to content inside <article>, <section>, and <h1><h6> tags.
  • Maintainability: Semantic code is self-documenting. Other developers instantly understand the purpose of <main>, <aside>, and <footer>.

Key semantic elements to know: <header>, <nav>, <main>, <article>, <section>, <aside>, <footer>, <figure>, <figcaption>, <time>, <details>, <summary>.

Q2: ARIA Roles and Live Regions

ARIA (Accessible Rich Internet Applications) attributes bridge the gap when native HTML semantics are insufficient.

```html

Form submitted successfully
\`\`\`

Common ARIA patterns:

  • aria-expanded: Indicates if a collapsible section is open or closed.
  • aria-hidden="true": Hides decorative elements from screen readers.
  • role="dialog" + aria-modal="true": Marks a modal dialog.
  • aria-describedby: Links an element to its description text.

Interview tip: Always prefer native HTML elements over ARIA. A <button> is better than a <div role="button"> because it comes with keyboard handling and focus behavior built in.

Q3: Focus Management and Keyboard Navigation

Accessible applications must be fully operable with a keyboard.

```javascript
// Trap focus inside a modal
function trapFocus(modal) {
const focusable = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];

modal.addEventListener('keydown', (e) => {
    if (e.key !== 'Tab') return;
    if (e.shiftKey && document.activeElement === first) {
        last.focus();
        e.preventDefault();
    } else if (!e.shiftKey && document.activeElement === last) {
        first.focus();
        e.preventDefault();
    }
});

}
```

Why it matters: Users who cannot use a mouse rely on Tab, Shift+Tab, Enter, Space, and Escape to navigate. Focus must be visible and logical.

Q4: Forms and Validation Accessibility

```html

\`\`\`

Key principles:

  • Every input must have a visible <label> (not just placeholder text).
  • Use aria-describedby to link inputs to error messages.
  • Set aria-invalid="true" when validation fails.
  • Group related fields with <fieldset> and <legend>.

CSS Deep Dives

Q5: Specificity

Specificity determines which CSS rule wins when multiple rules target the same element. Think of it as a four-digit number.

Specificity order:

  1. Inline styles (1000)
  2. IDs (100)
  3. Classes/attributes (10)
  4. Elements/pseudo-elements (1)

```css
#header { } /* 100 /
.nav { } /
10 /
nav { } /
1 /
#header .nav a { } /
112 = 100 + 10 + 1 + 1 */
```

Edge cases:

  • !important overrides all specificity but should be avoided in favor of proper cascade layers.
  • The :is() pseudo-class takes the specificity of its most specific argument.
  • The :not() pseudo-class itself has zero specificity; the specificity comes from its argument.

```css
/* :is() adopts highest specificity of its arguments /
:is(#header, .nav) a { } /
Specificity: 101 = 100 + 1 */
```

Interview tip: If you find yourself reaching for !important, it usually signals a CSS architecture problem. Consider using BEM naming, CSS modules, or cascade layers.

Q6: Flexbox vs Grid

Feature Flexbox Grid
Dimension 1D (row OR column) 2D (row AND column)
Alignment Main + Cross axis Rows + Columns
Use Case Navigation, cards Page layouts, forms
Content Flow Content-first (items flow) Layout-first (define structure)

```css
/* Flexbox: Component layout */
.card-container {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}

/* Grid: Page layout */
.page {
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
```

When to use which:

  • Use Flexbox when content determines the layout (navigation bars, card rows, centering a single item).
  • Use Grid when you want to define the layout structure first and place items into it (dashboard layouts, image galleries, form layouts).
  • They work together: use Grid for the page layout and Flexbox inside individual grid cells.

Q7: CSS Box Model and box-sizing

```css
/* Content Box (default) /
.box {
width: 200px;
padding: 20px;
border: 2px solid;
/
Total rendered width: 244px */
}

/* Border Box /
.box {
box-sizing: border-box;
width: 200px;
padding: 20px;
border: 2px solid;
/
Total rendered width: 200px (padding and border included) */
}
```

Best practice: Set box-sizing: border-box globally. This makes width and height calculations predictable.

```css
*, *::before, *::after {
box-sizing: border-box;
}
```

Q8: CSS Animations and Transitions

```css
/* Transitions: Animate state changes */
.button {
background: blue;
transform: translateY(0);
transition: background 0.3s ease, transform 0.2s ease-out;
}
.button:hover {
background: darkblue;
transform: translateY(-2px);
}

/* Animations: Keyframe-based */
@keyframes slide-in {
from {
opacity: 0;
transform: translateX(-100px);
}
to {
opacity: 1;
transform: translateX(0);
}
}

.fade-in {
animation: slide-in 0.5s ease forwards;
}
```

Performance tip: Animate only transform and opacity. These properties can be GPU-accelerated and avoid layout/paint. Avoid animating width, height, top, left, or margin because they trigger expensive layout recalculations.

```css
/* Bad: triggers layout */
.box { transition: width 0.3s; }

/* Good: GPU-accelerated */
.box { transition: transform 0.3s; }
```

Q9: Responsive Design and Media Queries

```css
/* Mobile-first approach */
.container {
padding: 1rem;
}

@media (min-width: 768px) {
.container {
padding: 2rem;
max-width: 720px;
margin: 0 auto;
}
}

@media (min-width: 1024px) {
.container {
max-width: 960px;
}
}
```

Modern responsive techniques:

  • Use clamp() for fluid typography: font-size: clamp(1rem, 2.5vw, 1.5rem).
  • Use CSS Grid with auto-fit and minmax() for auto-responsive grids.
  • Use container queries (newer browsers) for component-level responsiveness.

```css
/* Auto-responsive grid without media queries */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
```

JavaScript Essentials

Q10: Closures

A closure is a function that remembers the variables from its outer scope even after the outer function has returned.

```javascript
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count
};
}

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
```

Practical uses of closures:

  • Data privacy: Encapsulate state without classes.
  • Partial application / currying: Create specialized functions from general ones.
  • Event handlers: Remember which element triggered an action.

```javascript
// Classic interview question: closures in loops
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3
}

// Fix with let (block scoping)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}

// Fix with IIFE closure
for (var i = 0; i < 3; i++) {
((j) => setTimeout(() => console.log(j), 100))(i);
}
```

Q11: Event Loop

JavaScript is single-threaded but non-blocking thanks to the event loop. Understanding this is critical for interviews.

```javascript
console.log('1'); // Sync: call stack
setTimeout(() => console.log('2'), 0); // Macro task: web API -> callback queue
Promise.resolve().then(() => console.log('3')); // Micro task: microtask queue
console.log('4'); // Sync: call stack

// Output: 1, 4, 3, 2
```

Execution order:

  1. Synchronous code (call stack)
  2. Microtasks (Promises, queueMicrotask, MutationObserver)
  3. Macrotasks (setTimeout, setInterval, setImmediate)

Interview tip: Microtasks always run before the next macrotask. That is why Promises resolve before setTimeout, even with a 0ms delay.

```javascript
// Complex example
console.log('start');

setTimeout(() => console.log('timeout1'), 0);
setTimeout(() => console.log('timeout2'), 0);

Promise.resolve()
.then(() => console.log('promise1'))
.then(() => console.log('promise2'));

console.log('end');

// Output: start, end, promise1, promise2, timeout1, timeout2
```

Q12: Prototypes and Prototypal Inheritance

```javascript
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound`;
};

const dog = new Animal('Rex');
console.log(dog.speak()); // "Rex makes a sound"
```

How it works: When you call dog.speak(), JavaScript looks for speak on dog directly. Not finding it, it walks up the prototype chain to Animal.prototype, finds it, and executes.

Modern alternative: ES6 classes are syntactic sugar over prototypal inheritance.

```javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}

class Dog extends Animal {
speak() {
return `${this.name} barks`;
}
}
```

Q13: async/await and Promises

```javascript
// Basic async/await
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error('Not found');
return await response.json();
} catch (error) {
console.error('Fetch failed:', error);
throw error;
}
}
```

Parallel execution:
```javascript
// Bad: sequential waits
const user = await fetchUser(1);
const posts = await fetchPosts(1); // waits for fetchUser first

// Good: parallel with Promise.all
const [user, posts] = await Promise.all([
fetchUser(1),
fetchPosts(1)
]);
```

Error handling patterns:
```javascript
// Promise.all fails if ANY promise fails
// Promise.allSettled runs all promises regardless of outcome
const results = await Promise.allSettled([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
]);

results.forEach(result => {
if (result.status === 'fulfilled') {
console.log(result.value);
} else {
console.error(result.reason);
}
});
```

Q14: Debouncing and Throttling

```javascript
// Debounce: Wait until user stops triggering
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}

// Throttle: Execute at most once per interval
function throttle(fn, limit) {
let inThrottle = false;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
```

Use cases: Debounce search inputs (300ms), throttle scroll handlers (100ms).

React Questions

Q15: useEffect Cleanup

```javascript
useEffect(() => {
const subscription = api.subscribe(id);

// Cleanup function runs on unmount or before re-run
return () => {
    subscription.unsubscribe();
};

}, [id]); // Re-runs when id changes
```

Common mistakes:

  • Forgetting cleanup for subscriptions, timers, or event listeners causes memory leaks.
  • Using useEffect without a dependency array runs the effect after every render.
  • Omitting dependencies from the array leads to stale closures.

Q16: useMemo vs useCallback

```javascript
// useMemo: Memoize expensive computations
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.price - b.price);
}, [items]);

// useCallback: Memoize function references
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
```

When to use:

  • useMemo: Expensive computations (sorting, filtering large lists, complex calculations).
  • useCallback: Passing callbacks to optimized child components wrapped in React.memo.
  • Do not memoize everything. The overhead of memoization itself can outweigh benefits for trivial computations.

Q17: Custom Hooks

Custom hooks extract reusable stateful logic from components.

```javascript
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored !== null ? JSON.parse(stored) : initialValue;
});

useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);

return [value, setValue];

}

// Usage
const [theme, setTheme] = useLocalStorage('theme', 'light');
```

Rules of hooks:

  1. Only call hooks at the top level (never inside loops, conditions, or nested functions).
  2. Only call hooks from React functions (components or other custom hooks).

Q18: React Context and When to Use It

```javascript
const ThemeContext = createContext('light');

function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
```

When to use Context:

  • Theme, locale, authentication state, and other values needed by many distant components.
  • When not to use: For rapidly changing data (like form input). Context triggers re-renders of all consumers when the value changes.

Performance fix: Split state into separate contexts so consumers only re-render when their specific value changes.

```javascript
const ThemeStateContext = createContext();
const ThemeDispatchContext = createContext();

// Components that read theme don't re-render when dispatch changes
```

Q19: React.memo and Performance Optimization

```javascript
const ExpensiveList = React.memo(function ExpensiveList({ items }) {
return (


    {items.map(item => (
  • {item.name}

  • ))}

);
});
```

Re-render rules in React:

  1. State changes in the component itself.
  2. A parent component re-renders (all children re-render by default).
  3. The component consumes a context whose value changed.

Optimization checklist:

  • Profile with React DevTools before optimizing. Do not guess.
  • Move expensive computations into useMemo.
  • Use useCallback for functions passed to memoized children.
  • Consider virtualizing long lists with react-window or react-virtuoso.
  • Avoid inline object creation in JSX props: style={{ color: 'red' }} creates a new object every render.

Performance Optimization

Q20: Core Web Vitals

Metric Target What it Measures
LCP < 2.5s Largest Contentful Paint - how fast the main content loads
INP < 200ms Interaction to Next Paint - responsiveness to user input
CLS < 0.1 Cumulative Layout Shift - visual stability

Additional metrics to know:

  • FCP (First Contentful Paint): Time until the first piece of content appears. Target: < 1.8s.
  • TBT (Total Blocking Time): Sum of blocking time during FCP to TTI. Target: < 200ms.
  • TTI (Time to Interactive): Time until the page is fully interactive.

Q21: Optimization Techniques

1. Code Splitting: Break your bundle into smaller chunks loaded on demand.
```javascript
const Dashboard = React.lazy(() => import('./Dashboard'));

function App() {
return (
<Suspense fallback={}>


);
}
```

2. Image Optimization:

  • Use modern formats: WebP or AVIF.
  • Serve responsive images with srcset and sizes.
  • Lazy load images below the fold with loading="lazy".
  • Use <picture> element for art direction.

3. Bundle Analysis:

  • Run npx vite-bundle-visualizer or webpack-bundle-analyzer to find large dependencies.
  • Replace heavy libraries with lighter alternatives (date-fns instead of moment, preact instead of react for small widgets).

4. Caching Strategies:

  • Use service workers for offline-first experiences.
  • Set proper Cache-Control headers.
  • Use stale-while-revalidate pattern for API responses.

5. Rendering Performance:

  • Avoid layout thrashing by batching DOM reads and writes.
  • Use requestAnimationFrame for visual updates.
  • Debounce resize and scroll event handlers.

System Design for Frontend

Q22: Component Architecture

```text
App
├── Layout
│ ├── Header
│ │ ├── Logo
│ │ ├── Navigation
│ │ └── UserMenu
│ ├── Main
│ │ ├── Content
│ │ └── Sidebar
│ └── Footer
└── Routes
├── Home
├── Profile
└── Settings
```

Design principles:

  • Single Responsibility: Each component does one thing well.
  • Composition over Inheritance: Build complex components by composing small ones.
  • Separation of Concerns: Separate UI rendering from business logic. Use hooks or services for data fetching.
  • Container/Presentational Pattern: Containers handle logic and data; presentational components handle rendering.

Q23: State Management

Solution Use Case Re-render Cost
useState Local component state Component only
useReducer Complex local state Component only
Context Light global state All consumers
Redux/Zustand Heavy global state Selective (selectors)

Decision framework:

  • Start with useState. It is sufficient for most component-local state.
  • Move to useReducer when state logic becomes complex or involves multiple sub-values.
  • Use Context for values shared across many components (theme, auth, locale).
  • Introduce a state library (Zustand, Jotai, Redux Toolkit) when you need fine-grained subscriptions, middleware (logging, persistence), or complex cross-cutting state.

Q24: Data Fetching Patterns

```javascript
// Server Component approach (Next.js)
async function UserPage({ params }) {
const user = await fetchUser(params.id);
return ;
}

// Client Component approach
function UserPage({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
    fetchUser(userId)
        .then(setUser)
        .finally(() => setLoading(false));
}, [userId]);

if (loading) return <Spinner />;
return <UserProfile user={user} />;

}
```

Modern patterns: React Query (TanStack Query) and SWR handle caching, deduplication, background refetching, and optimistic updates out of the box.

Common Mistakes and How to Avoid Them

1. Not Using Key Properly on Lists

```javascript
// Bad: Using array index as key (causes reorder bugs)
{items.map((item, index) => )}

// Good: Use stable, unique identifiers
{items.map(item => )}
```

2. Memory Leaks from Unsubscribed Effects

```javascript
// Bad: No cleanup
useEffect(() => {
const timer = setInterval(() => {
fetchUpdates();
}, 5000);
}, []); // Timer leaks on unmount

// Good: Cleanup
useEffect(() => {
const timer = setInterval(() => {
fetchUpdates();
}, 5000);
return () => clearInterval(timer);
}, []);
```

3. Stale Closures

```javascript
// Bad: count is stale inside the interval
useEffect(() => {
const id = setInterval(() => {
console.log(count); // Always logs initial value
}, 1000);
return () => clearInterval(id);
}, []);

// Good: Use functional updater or ref
useEffect(() => {
const id = setInterval(() => {
setCount(prev => prev + 1); // Always uses latest value
}, 1000);
return () => clearInterval(id);
}, []);
```

4. CSS Specificity Wars

```css
/* Bad: Using !important to override */
.button.primary { color: white !important; }

/* Good: Use more specific selector or refactor */
.button.primary { color: white; }
```

5. Ignoring Accessibility

  • Always test with a screen reader (VoiceOver on Mac, NVDA on Windows).
  • Check color contrast ratios (minimum 4.5:1 for normal text).
  • Ensure all interactive elements are keyboard accessible.
  • Do not use color alone to convey information (add icons or text).

6. Premature Optimization

  • Profile before optimizing. Use React DevTools Profiler and Chrome Lighthouse.
  • Do not wrap everything in React.memo or useMemo by default.
  • Optimize only the bottlenecks identified by measurement, not by assumption.

7. Not Handling Error Boundaries

```javascript
class ErrorBoundary extends React.Component {
state = { hasError: false };

static getDerivedStateFromError(error) {
    return { hasError: true };
}

componentDidCatch(error, errorInfo) {
    logErrorToService(error, errorInfo);
}

render() {
    if (this.state.hasError) {
        return <FallbackUI />;
    }
    return this.props.children;
}

}
```

Error boundaries catch rendering errors in child components. Without them, a single component crash takes down the entire app.

Resources

frontend interview HTML CSS JavaScript React TypeScript

Continue Your Prep

Apply what you learned with our structured roadmaps and practice problems.