Quick answer
Web performance interview questions are usually about judgment: what you would measure first, how you would isolate the bottleneck, and which optimization is actually worth the complexity. Strong answers stay connected to user impact.
Start here: Frontend Engineer Interview Prep
Read this next
- Frontend Engineer Interview Questions
- Full Stack Developer Interview Questions
- Technical Interview Questions Hub
Why Performance Matters
Web performance directly impacts user experience, conversion rates, SEO rankings, and accessibility. Studies consistently show that users abandon sites that take more than 3 seconds to load, and each additional second of load time can reduce conversions by 7%. Top tech companies prioritize performance, making it a common interview topic.
Core Web Vitals
Google's Core Web Vitals are the key metrics you need to understand and optimize.
Largest Contentful Paint (LCP) measures loading performance—specifically, how long it takes for the largest content element to become visible. Target: under 2.5 seconds.
First Input Delay (FID) / Interaction to Next Paint (INP) measures interactivity—the time from when a user first interacts with your page to when the browser responds. Target: under 100ms for FID, under 200ms for INP.
Cumulative Layout Shift (CLS) measures visual stability—how much the page layout shifts during loading. Target: under 0.1.
Practice this with Interview Masters
Generate custom frontend optimization drills in Interview Masters so you can practice performance reasoning with realistic follow-up questions.
Optimizing LCP
<!-- Preload critical resources -->
<link rel="preload" href="/hero-image.webp" as="image" />
<link rel="preload" href="/critical-font.woff2" as="font" crossorigin />
<!-- Preconnect to required origins -->
<link rel="preconnect" href="https://api.example.com" />
<link rel="dns-prefetch" href="https://analytics.example.com" />
// Lazy load below-the-fold images
<img
src="placeholder.jpg"
data-src="actual-image.jpg"
loading="lazy"
decoding="async"
/>
// Priority hints for critical resources
<img src="hero.jpg" fetchpriority="high" />
<script src="non-critical.js" fetchpriority="low"></script>
Image Optimization Strategies
<!-- Responsive images with srcset -->
<img
src="image-800.jpg"
srcset="
image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w
"
sizes="(max-width: 600px) 100vw, 50vw"
alt="Description"
loading="lazy"
/>
<!-- Modern formats with fallback -->
<picture>
<source srcset="image.avif" type="image/avif" />
<source srcset="image.webp" type="image/webp" />
<img src="image.jpg" alt="Description" />
</picture>
Optimizing JavaScript
JavaScript is often the biggest performance bottleneck. Here's how to optimize it.
Code Splitting
// Route-based splitting in React
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
// Component-level splitting
const HeavyChart = lazy(() =>
import('./HeavyChart').then(module => ({
default: module. HeavyChart
}))
);
// Prefetching for anticipated navigation
const prefetchSettings = () => {
import('./Settings');
};
<Link to="/settings" onMouseEnter={prefetchSettings}>
Settings
</Link>
Tree Shaking
// Bad-imports entire library
import _ from 'lodash';
const result = _.map(array, fn);
// Good-imports only what's needed
import map from 'lodash/map';
const result = map(array, fn);
// Even better-use native methods
const result = array.map(fn);
// Enable tree shaking in package.json
{
"sideEffects": false,
// or specify files with side effects
"sideEffects": ["*.css", "*.global.js"]
}
Avoid Blocking the Main Thread
// Break up long tasks
async function processLargeArray(items) {
const CHUNK_SIZE = 100;
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
const chunk = items.slice(i, i + CHUNK_SIZE);
processChunk(chunk);
// Yield to the main thread
await new Promise(resolve => setTimeout(resolve, 0));
}
}
// Use Web Workers for CPU-intensive tasks
const worker = new Worker('heavy-computation.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (event) => {
console.log('Computed result:', event.data);
};
// Debounce expensive operations
function debounce(fn, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
const handleSearch = debounce((query) => {
fetchResults(query);
}, 300);
Optimizing CLS
Layout shifts frustrate users and hurt Core Web Vitals scores.
/*Always reserve space for images*/
img {
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
object-fit: cover;
}
/*Reserve space for dynamic content*/
.ad-container {
min-height: 250px;
}
/*Prevent font swap layout shift*/
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap;
size-adjust: 105%; /*Match fallback font metrics*/
}
/* Use content-visibility for off-screen optimization */
.below-fold-section {
content-visibility: auto;
contain-intrinsic-size: 0 500px;
}
Caching Strategies
// Service Worker caching
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll([
'/',
'/styles.css',
'/app.js',
'/offline.html'
]);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// Return cached version or fetch from network
return response || fetch(event.request).then((response) => {
// Cache new responses
if (response.status === 200) {
const clone = response.clone();
caches.open('v1').then((cache) => {
cache.put(event.request, clone);
});
}
return response;
});
})
);
});
HTTP Cache Headers
# Static assets-cache for 1 year
Cache-Control: public, max-age=31536000, immutable
# HTML pages-revalidate each time
Cache-Control: no-cache, must-revalidate
# API responses-cache for 5 minutes
Cache-Control: private, max-age=300
# Stale-while-revalidate pattern
Cache-Control: max-age=60, stale-while-revalidate=3600
React-Specific Optimizations
// Memoize expensive components
const ExpensiveList = React.memo(({ items }) => {
return items.map(item => <ListItem key={item.id} {...item} />);
});
// Memoize expensive calculations
function DataGrid({ data, filter }) {
const filteredData = useMemo(() => {
return data.filter(item => matchesFilter(item, filter));
}, [data, filter]);
return <Grid data={filteredData} />;
}
// Memoize callbacks passed to children
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return (
<div>
<p>Count: {count}</p>
<ExpensiveChild onClick={handleClick} />
</div>
);
}
// Virtualize long lists
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList>
);
}
Measuring Performance
// Performance Observer API
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime, entry.duration);
}
});
observer.observe({ entryTypes: ['largest-contentful-paint', 'layout-shift'] });
// Web Vitals library
import { onCLS, onFID, onLCP, onINP } from 'web-vitals';
onCLS(console.log);
onFID(console.log);
onLCP(console.log);
onINP(console.log);
// Custom performance marks
performance.mark('feature-start');
//... expensive operation
performance.mark('feature-end');
performance.measure('feature-duration', 'feature-start', 'feature-end');
Performance Budgets
Establish and enforce performance budgets to prevent regression.
{
"budgets": [
{
"resourceType": "script",
"budget": 300
},
{
"resourceType": "total",
"budget": 500
},
{
"metric": "first-contentful-paint",
"budget": 2000
},
{
"metric": "interactive",
"budget": 5000
}
]
}
Interview Discussion Points
When discussing performance in interviews, be prepared to explain how you identify performance bottlenecks using Chrome DevTools, Lighthouse, and real user monitoring. Discuss the trade-offs between different optimization strategies—for example, the balance between caching aggressively and cache invalidation complexity. Show awareness of how performance impacts accessibility, particularly for users on slow connections or older devices.
Concrete examples from your experience are invaluable. Describe a specific performance problem you identified, the investigation process, the solution you implemented, and the measurable improvement achieved. This demonstrates practical application of performance knowledge, not just theoretical understanding.
