Split View: 브라우저는 어떻게 동작하는가: URL 입력부터 픽셀 렌더링까지 — 프론트엔드 면접 필수 지식
브라우저는 어떻게 동작하는가: URL 입력부터 픽셀 렌더링까지 — 프론트엔드 면접 필수 지식
- 소개
- 1. URL 입력부터 화면 표시까지 (면접 완벽 답변)
- 2. 네트워크 단계
- 3. HTML 파싱
- 4. CSS 파싱
- 5. 렌더 트리
- 6. 레이아웃 (Reflow)
- 7. 페인트와 합성
- 8. Critical Rendering Path 최적화
- 9. 리플로우 vs 리페인트
- 10. 이벤트 루프와 태스크 큐
- 11. Web Workers와 OffscreenCanvas
- 12. DevTools Performance 패널 가이드
- 13. 면접 질문 및 퀴즈
- 참고 자료
소개
"브라우저 주소창에 URL을 입력하면 무슨 일이 일어나나요?"
이 질문은 프론트엔드 면접에서 가장 자주 등장하는 질문 중 하나이며, 브라우저의 동작 원리에 대한 깊은 이해를 확인하는 핵심 질문입니다. 단순히 "HTML을 받아서 화면에 보여준다"는 답변으로는 부족합니다.
이 글에서는 DNS 조회부터 TCP 연결, HTML/CSS 파싱, DOM/CSSOM 구축, 렌더 트리 생성, 레이아웃, 페인트, 합성, 그리고 이벤트 루프까지 브라우저 동작의 모든 단계를 체계적으로 다룹니다.
1. URL 입력부터 화면 표시까지 (면접 완벽 답변)
1.1 전체 흐름 요약
사용자: URL 입력 + Enter
|
v
1. URL 파싱 (프로토콜, 도메인, 경로 분리)
|
v
2. DNS 조회 (도메인 → IP 주소)
|
v
3. TCP 연결 (3-way handshake)
|
v
4. TLS 핸드셰이크 (HTTPS인 경우)
|
v
5. HTTP 요청/응답
|
v
6. HTML 파싱 → DOM 트리 구축
|
v
7. CSS 파싱 → CSSOM 트리 구축
|
v
8. DOM + CSSOM → 렌더 트리 생성
|
v
9. 레이아웃 (각 요소의 위치/크기 계산)
|
v
10. 페인트 (픽셀로 그리기)
|
v
11. 합성 (레이어 합치기, GPU)
|
v
화면에 표시!
2. 네트워크 단계
2.1 DNS 조회 (Domain Name System)
브라우저가 도메인 이름을 IP 주소로 변환하는 과정입니다.
조회 순서:
1. 브라우저 DNS 캐시 확인
2. OS DNS 캐시 확인
3. 로컬 hosts 파일 확인
4. DNS Resolver (ISP) 조회
5. Root DNS 서버
6. TLD DNS 서버 (.com, .kr 등)
7. 권한 있는 DNS 서버 (실제 IP 반환)
예시: www.example.com 조회
Browser Cache → miss
OS Cache → miss
Router Cache → miss
ISP DNS Resolver → miss
Root Server → ".com은 여기에 물어봐"
.com TLD Server → "example.com은 여기에 물어봐"
example.com NS → "IP는 93.184.216.34"
최적화 기법:
dns-prefetch: 미리 DNS를 해석합니다
<link rel="dns-prefetch" href="//api.example.com" />
<link rel="preconnect" href="https://cdn.example.com" />
2.2 TCP 3-Way Handshake
Client Server
| |
|--- SYN (seq=x) ------->|
| |
|<-- SYN-ACK (seq=y, -----|
| ack=x+1) |
| |
|--- ACK (ack=y+1) ----->|
| |
연결 완료!
2.3 TLS 핸드셰이크
HTTPS 연결 시 추가되는 암호화 과정입니다.
Client Server
| |
|-- ClientHello (TLS 버전, -->|
| 암호 스위트 목록) |
| |
|<-- ServerHello, 인증서, ---|
| 서버 키 교환 |
| |
|-- 클라이언트 키 교환, -->|
| ChangeCipherSpec, |
| Finished |
| |
|<-- ChangeCipherSpec, ---|
| Finished |
| |
암호화된 통신 시작!
2.4 HTTP 요청/응답
GET / HTTP/2
Host: www.example.com
Accept: text/html,application/xhtml+xml
Accept-Encoding: gzip, br
Connection: keep-alive
HTTP/2 200 OK
Content-Type: text/html; charset=UTF-8
Content-Encoding: gzip
Cache-Control: max-age=3600
Content-Length: 12345
<!DOCTYPE html>
<html>...
3. HTML 파싱
3.1 토크나이저와 트리 구축
HTML 파서는 바이트 스트림을 토큰으로 변환하고, 토큰을 DOM 노드로 구축합니다.
바이트 → 문자 → 토큰 → 노드 → DOM 트리
예시 HTML:
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1>Hello</h1>
<p>World</p>
</body>
</html>
DOM 트리:
Document
└─ html
├─ head
│ └─ title
│ └─ "My Page"
└─ body
├─ h1
│ └─ "Hello"
└─ p
└─ "World"
3.2 스크립트 차단 (Parser Blocking)
기본적으로 script 태그를 만나면 HTML 파싱이 중단됩니다.
<!-- 파서 차단: HTML 파싱이 중단되고, 스크립트 다운로드 + 실행 후 재개 -->
<script src="app.js"></script>
<!-- defer: HTML 파싱과 병렬 다운로드, DOM 완성 후 실행 -->
<script defer src="app.js"></script>
<!-- async: HTML 파싱과 병렬 다운로드, 다운로드 완료 시 즉시 실행 -->
<script async src="analytics.js"></script>
defer vs async 비교:
일반 script:
HTML: ───▓▓▓▓▓▓▓▓▓▓|████|▓▓▓▓▓▓▓
JS: |다운|실행|
defer:
HTML: ───▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓|실행|
JS: |████ 다운로드 ████|
async:
HTML: ───▓▓▓▓▓▓▓▓|실행|▓▓▓▓▓▓▓▓
JS: |████ 다운|
defer: 순서 보장, DOMContentLoaded 전에 실행async: 순서 보장 안됨, 다운로드 완료 즉시 실행
3.3 Preload Scanner
HTML 파서가 차단되어도 프리로드 스캐너는 계속 작동하여 리소스를 미리 발견합니다.
<!-- preload: 중요 리소스 사전 로딩 -->
<link rel="preload" href="critical.css" as="style" />
<link rel="preload" href="hero.webp" as="image" />
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
<!-- prefetch: 다음 페이지에 필요한 리소스 미리 가져오기 -->
<link rel="prefetch" href="/next-page.html" />
4. CSS 파싱
4.1 CSSOM 구축
CSS도 HTML과 유사한 과정을 거칩니다.
바이트 → 문자 → 토큰 → 노드 → CSSOM 트리
CSS:
body { font-size: 16px; }
h1 { color: blue; font-size: 2em; }
p { color: #333; }
CSSOM 트리:
body (font-size: 16px)
├─ h1 (color: blue, font-size: 32px)
└─ p (color: #333)
4.2 CSS는 렌더 차단 리소스
CSS가 로드되기 전에는 렌더 트리를 구축할 수 없으므로 CSS는 렌더 차단 리소스입니다.
<!-- 렌더 차단: 이 CSS가 로드/파싱될 때까지 렌더링 불가 -->
<link rel="stylesheet" href="styles.css" />
<!-- 조건부: 인쇄용은 렌더 차단하지 않음 -->
<link rel="stylesheet" href="print.css" media="print" />
<!-- 조건부: 특정 뷰포트에서만 렌더 차단 -->
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)" />
4.3 Specificity (명시도) 계산
!important > 인라인 스타일 > ID > 클래스/속성/의사클래스 > 태그/의사요소
계산법: (a, b, c)
- a: ID 선택자 수
- b: 클래스, 속성, 의사클래스 수
- c: 태그, 의사요소 수
예시:
#header .nav a → (1, 1, 1)
.nav .item.active → (0, 3, 0)
nav ul li a → (0, 0, 4)
#main #content p.text → (2, 1, 1)
4.4 캐스케이드 규칙
우선순위 (높은 순):
1. !important가 붙은 사용자 에이전트 스타일
2. !important가 붙은 사용자 스타일
3. !important가 붙은 작성자 스타일
4. 작성자 스타일 (명시도 순)
5. 사용자 스타일
6. 사용자 에이전트 스타일 (브라우저 기본)
5. 렌더 트리
5.1 DOM + CSSOM = 렌더 트리
DOM 트리: CSSOM:
html body { font: 16px }
├─ head h1 { color: blue }
│ └─ title p { color: #333 }
├─ body .hidden { display: none }
│ ├─ h1
│ ├─ p
│ └─ div.hidden
│ └─ span (visibility: hidden)
렌더 트리 (보이는 요소만):
html
└─ body (font: 16px)
├─ h1 (color: blue)
├─ p (color: #333)
└─ span (visibility: hidden) ← 포함! 공간 차지
[div.hidden은 제외 - display: none]
5.2 display: none vs visibility: hidden
| 속성 | 렌더 트리 포함 | 공간 차지 | 리플로우 발생 |
|---|---|---|---|
display: none | 아니오 | 아니오 | 추가/제거 시 발생 |
visibility: hidden | 예 | 예 | 아니오 |
opacity: 0 | 예 | 예 | 아니오 |
5.3 렌더 트리 구축 과정
- DOM 트리의 루트부터 순회
- 보이지 않는 노드 건너뜀 (script, meta, display: none)
- 각 보이는 노드에 CSSOM 규칙 매칭
- 계산된 스타일과 함께 노드를 렌더 트리에 추가
6. 레이아웃 (Reflow)
6.1 Box Model
모든 요소는 Box Model로 표현됩니다.
┌─────────────────────────────────────┐
│ margin │
│ ┌───────────────────────────────┐ │
│ │ border │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ padding │ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ content │ │ │ │
│ │ │ │ (width x height)│ │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └───────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
box-sizing: content-box (기본)
width = content width만
실제 너비 = width + padding + border
box-sizing: border-box (권장)
width = content + padding + border
실제 너비 = width
6.2 Block vs Inline
Block 요소 (div, p, h1...):
┌──────────────────────────────┐
│ Block 1 (전체 너비 차지) │
├──────────────────────────────┤
│ Block 2 │
├──────────────────────────────┤
│ Block 3 │
└──────────────────────────────┘
Inline 요소 (span, a, strong...):
[inline1][inline2][inline3]
[inline4가 길어서 다음 줄로...]
Inline-block:
[inline-block1] [inline-block2]
(블록처럼 너비/높이 설정 가능, 인라인처럼 나란히)
6.3 레이아웃 과정
- 루트에서 시작: 뷰포트 크기 결정
- 블록 레이아웃: 위에서 아래로 블록 배치
- 인라인 레이아웃: 왼쪽에서 오른쪽으로 인라인 배치
- Float 처리: 텍스트 흐름 조정
- 위치 계산: relative, absolute, fixed, sticky 처리
- 크기 확정: 모든 요소의 최종 위치와 크기 계산
7. 페인트와 합성
7.1 페인트 (Paint)
레이아웃이 완료되면 각 요소를 실제 픽셀로 그립니다.
페인트 순서 (z-order):
1. 배경 색상
2. 배경 이미지
3. 보더
4. 자식 요소
5. 아웃라인
페인트는 여러 레이어에서 수행됩니다.
7.2 레이어 생성 조건
다음 조건에서 새 합성 레이어가 생성됩니다:
transform: translate3d()또는translateZ()will-change: transform또는will-change: opacityposition: fixed- CSS 애니메이션/트랜지션이 적용된 opacity, transform
- 비디오, 캔버스 요소
filter속성 사용
/* 합성 레이어 강제 생성 */
.layer {
will-change: transform;
/* 또는 */
transform: translateZ(0);
}
7.3 합성 (Compositing)
GPU가 각 레이어를 최종적으로 합치는 단계입니다.
합성 단계:
1. 레이어 나누기 (Layer Tree 생성)
2. 각 레이어를 타일(Tile)로 분할
3. 타일을 래스터화 (GPU에서 비트맵으로 변환)
4. 드로 쿼드 생성 (화면 위치 정보)
5. 컴포지터 프레임 생성 (최종 합성)
6. GPU에서 화면에 표시
렌더링 경로 비교:
JS/CSS 변경
→ Style → Layout → Paint → Composite (전체 파이프라인)
→ Style → Paint → Composite (레이아웃 스킵)
→ Style → Composite (가장 빠름!)
7.4 GPU 가속 속성
/* 합성만 일어나는 속성 (가장 빠름) */
.fast {
transform: translate(10px, 20px); /* 위치 이동 */
transform: scale(1.2); /* 크기 변경 */
transform: rotate(45deg); /* 회전 */
opacity: 0.5; /* 투명도 */
}
/* 페인트 발생 (중간) */
.medium {
color: red;
background-color: blue;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
/* 레이아웃 발생 (가장 느림) */
.slow {
width: 200px;
height: 100px;
margin: 10px;
padding: 20px;
font-size: 18px;
}
8. Critical Rendering Path 최적화
8.1 렌더 차단 리소스 제거
<!-- CSS: Critical CSS 인라인화 -->
<style>
/* 첫 화면에 필요한 최소 CSS만 인라인 */
body { margin: 0; font-family: system-ui; }
.hero { background: #3b82f6; color: white; padding: 2rem; }
</style>
<!-- 나머지 CSS는 비동기 로딩 -->
<link rel="preload" href="styles.css" as="style"
onload="this.onload=null;this.rel='stylesheet'" />
<noscript><link rel="stylesheet" href="styles.css" /></noscript>
<!-- JS: 파서 차단 방지 -->
<script defer src="app.js"></script>
8.2 리소스 힌트
<!-- DNS 사전 조회 -->
<link rel="dns-prefetch" href="//api.example.com" />
<!-- 사전 연결 (DNS + TCP + TLS) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<!-- 중요 리소스 사전 로딩 -->
<link rel="preload" href="hero.webp" as="image" />
<link rel="preload" href="critical.js" as="script" />
<!-- 다음 네비게이션 리소스 미리 가져오기 -->
<link rel="prefetch" href="/about" />
<!-- 다음 페이지 전체 미리 렌더링 -->
<link rel="prerender" href="/likely-next-page" />
8.3 최적화 체크리스트
- CSS를 head에 배치: 렌더 차단을 최소화
- Critical CSS 인라인: 첫 화면에 필요한 CSS만
- JS에 defer/async 사용: 파서 차단 방지
- 리소스 압축: gzip/brotli 활용
- 이미지 최적화: WebP/AVIF, lazy loading, srcset
- 폰트 최적화: font-display: swap, preload
- HTTP/2 사용: 멀티플렉싱으로 병렬 요청
9. 리플로우 vs 리페인트
9.1 리플로우 (Reflow / Layout)
레이아웃을 재계산하는 비용이 큰 작업입니다.
리플로우 트리거:
- 요소 추가/제거
- 요소 크기 변경 (width, height, padding, margin, border)
- 폰트 크기 변경
- 텍스트 내용 변경
- 윈도우 리사이즈
- 계산된 스타일 읽기 (offsetWidth, getComputedStyle 등)
9.2 리페인트 (Repaint)
시각적 속성만 변경되는 상대적으로 저렴한 작업입니다.
리페인트만 트리거 (리플로우 없음):
- color, background-color
- visibility
- box-shadow
- outline
9.3 DOM 읽기/쓰기 배칭
// 나쁜 예: 읽기-쓰기가 교차하여 강제 리플로우 발생
const items = document.querySelectorAll('.item');
items.forEach(item => {
const width = item.offsetWidth; // 읽기 → 강제 리플로우!
item.style.width = width + 10 + 'px'; // 쓰기
});
// 좋은 예: 읽기를 먼저, 쓰기를 나중에 (배칭)
const widths = [];
items.forEach(item => {
widths.push(item.offsetWidth); // 모든 읽기를 먼저
});
items.forEach((item, i) => {
item.style.width = widths[i] + 10 + 'px'; // 모든 쓰기를 나중에
});
9.4 Virtual DOM의 존재 이유
직접 DOM 조작:
변경 1 → 리플로우 → 리페인트
변경 2 → 리플로우 → 리페인트
변경 3 → 리플로우 → 리페인트
Virtual DOM (React):
변경 1, 2, 3을 메모리에서 계산 (diff)
→ 최소한의 실제 DOM 변경
→ 리플로우 1회 → 리페인트 1회
10. 이벤트 루프와 태스크 큐
10.1 JavaScript 런타임 구조
┌─────────────────────────┐
│ Call Stack │
│ (함수 실행 컨텍스트) │
└───────────┬─────────────┘
│
┌───────────▼─────────────┐
│ Event Loop │
│ (스택이 비면 큐 확인) │
└───┬─────────────────┬───┘
│ │
┌───▼───┐ ┌────▼────┐
│Micro │ │ Macro │
│Task │ │ Task │
│Queue │ │ Queue │
└───────┘ └─────────┘
10.2 Macrotask vs Microtask
console.log('1: 동기');
setTimeout(() => {
console.log('2: Macrotask (setTimeout)');
}, 0);
Promise.resolve().then(() => {
console.log('3: Microtask (Promise)');
});
queueMicrotask(() => {
console.log('4: Microtask (queueMicrotask)');
});
console.log('5: 동기');
// 출력 순서:
// 1: 동기
// 5: 동기
// 3: Microtask (Promise)
// 4: Microtask (queueMicrotask)
// 2: Macrotask (setTimeout)
Macrotask (Task Queue):
- setTimeout, setInterval
- I/O 콜백
- UI 렌더링
- requestAnimationFrame
Microtask (Microtask Queue):
- Promise.then/catch/finally
- queueMicrotask
- MutationObserver
10.3 이벤트 루프 실행 순서
1. Call Stack의 모든 동기 코드 실행
2. Call Stack이 비면:
a. Microtask Queue의 모든 태스크 실행 (큐가 빌 때까지)
b. 렌더링이 필요하면 렌더링 수행
c. Macrotask Queue에서 하나의 태스크를 가져와 실행
3. 2번으로 돌아가 반복
10.4 requestAnimationFrame (rAF)
// rAF: 다음 리페인트 직전에 콜백 실행
// 보통 60fps = 약 16.67ms 간격
function animate() {
// 애니메이션 로직
element.style.transform = `translateX(${position}px)`;
position += 2;
if (position < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
rAF vs setTimeout:
// 나쁜 예: setTimeout으로 애니메이션
// 프레임 누락, 불안정한 간격
setInterval(() => {
moveElement();
}, 16);
// 좋은 예: rAF 사용
// 브라우저 리페인트와 동기화, 부드러운 60fps
function animate() {
moveElement();
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
10.5 requestIdleCallback
// 브라우저가 유휴 상태일 때 실행
// 중요하지 않은 작업에 사용
requestIdleCallback((deadline) => {
// deadline.timeRemaining()으로 남은 시간 확인
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
performTask(tasks.shift());
}
if (tasks.length > 0) {
requestIdleCallback(processRemainingTasks);
}
});
11. Web Workers와 OffscreenCanvas
11.1 Web Worker
메인 스레드를 차단하지 않고 백그라운드에서 JavaScript를 실행합니다.
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ type: 'HEAVY_CALC', data: bigArray });
worker.onmessage = (event) => {
console.log('결과:', event.data.result);
};
// worker.js
self.onmessage = (event) => {
if (event.data.type === 'HEAVY_CALC') {
const result = heavyComputation(event.data.data);
self.postMessage({ result });
}
};
function heavyComputation(data) {
// CPU 집약적 작업 (정렬, 암호화, 이미지 처리 등)
return data.sort((a, b) => a - b);
}
11.2 Worker 종류
// 1. Dedicated Worker: 1:1 관계
const dedicated = new Worker('worker.js');
// 2. Shared Worker: 여러 탭/iframe에서 공유
const shared = new SharedWorker('shared.js');
shared.port.start();
shared.port.postMessage('hello');
// 3. Service Worker: 네트워크 프록시, 오프라인 지원
navigator.serviceWorker.register('/sw.js');
11.3 OffscreenCanvas
Worker에서 캔버스 렌더링을 수행합니다.
// main.js
const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('render-worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
// render-worker.js
self.onmessage = (event) => {
const canvas = event.data.canvas;
const ctx = canvas.getContext('2d');
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 복잡한 렌더링 로직...
ctx.fillStyle = '#3b82f6';
ctx.fillRect(x, y, 50, 50);
requestAnimationFrame(draw);
}
draw();
};
12. DevTools Performance 패널 가이드
12.1 Performance 탭 사용법
1. DevTools 열기 (F12 또는 Cmd+Option+I)
2. Performance 탭 선택
3. 녹화 시작 (Ctrl+E)
4. 페이지 조작
5. 녹화 중지
결과 해석:
- FPS 차트: 녹색이 60fps, 빨간색이 프레임 드롭
- CPU 차트: 색상별 작업 종류
- 노란색: JavaScript 실행
- 보라색: 레이아웃 (리플로우)
- 녹색: 페인트
- 회색: 기타
12.2 주요 메트릭
Core Web Vitals:
- LCP (Largest Contentful Paint): 2.5초 이내 → 좋음
- INP (Interaction to Next Paint): 200ms 이내 → 좋음
- CLS (Cumulative Layout Shift): 0.1 이하 → 좋음
추가 메트릭:
- FCP (First Contentful Paint): 첫 콘텐츠 표시
- TTFB (Time to First Byte): 서버 응답 시간
- TBT (Total Blocking Time): 메인 스레드 차단 시간
12.3 일반적인 성능 문제 패턴
1. 긴 태스크 (Long Task):
- 50ms 이상 메인 스레드 차단
- 해결: 코드 분할, Web Worker 활용
2. 레이아웃 트래싱 (Layout Thrashing):
- 읽기/쓰기 교차로 강제 리플로우 반복
- 해결: 읽기/쓰기 배칭
3. 과도한 페인트:
- 불필요한 영역 재페인트
- 해결: will-change, transform 사용
4. 큰 DOM 트리:
- 1500+ 노드는 성능 저하
- 해결: 가상화 (react-virtualized, tanstack-virtual)
13. 면접 질문 및 퀴즈
면접 질문 10선
Q1: 브라우저 주소창에 URL을 입력하면 어떤 일이 일어나나요?
URL 파싱 후 DNS 조회로 IP를 얻고, TCP 3-way handshake로 연결합니다. HTTPS라면 TLS 핸드셰이크도 수행합니다. HTTP 요청으로 HTML을 받으면 파서가 DOM 트리를 구축하고, CSS를 파싱하여 CSSOM을 만듭니다. DOM과 CSSOM을 결합하여 렌더 트리를 생성하고, 레이아웃으로 위치/크기를 계산하고, 페인트로 픽셀을 그리고, 합성으로 GPU에서 최종 화면을 표시합니다.
Q2: Critical Rendering Path를 설명하고 최적화 방법은?
CRP는 HTML 수신부터 첫 화면 렌더링까지의 경로입니다. CSS는 렌더 차단 리소스이므로 Critical CSS를 인라인하고, JS는 defer/async로 파서 차단을 방지합니다. preload로 중요 리소스를 미리 로드하고, 불필요한 CSS/JS를 제거합니다.
Q3: 리플로우와 리페인트의 차이는?
리플로우는 레이아웃 재계산(위치, 크기 변경)으로 비용이 큽니다. 리페인트는 시각적 속성(색상, 그림자)만 변경하는 상대적으로 저렴한 작업입니다. 리플로우는 항상 리페인트를 동반하지만, 리페인트는 리플로우 없이 발생할 수 있습니다.
Q4: display: none과 visibility: hidden의 차이는?
display: none은 렌더 트리에서 완전히 제거되어 공간을 차지하지 않습니다. 토글 시 리플로우가 발생합니다. visibility: hidden은 렌더 트리에 남아 공간을 차지하며, 리페인트만 발생합니다.
Q5: 이벤트 루프에서 Microtask와 Macrotask의 실행 순서는?
동기 코드 실행 후 Call Stack이 비면, Microtask Queue의 모든 태스크를 먼저 처리합니다. Microtask Queue가 비면 렌더링이 필요하면 수행하고, Macrotask Queue에서 하나의 태스크를 실행합니다. 이를 반복합니다.
Q6: requestAnimationFrame의 역할과 setTimeout과의 차이는?
rAF는 다음 리페인트 직전에 콜백을 실행하여 60fps 애니메이션을 보장합니다. setTimeout은 정확한 타이밍을 보장하지 않고, 브라우저 리프레시 레이트와 동기화되지 않아 프레임 누락이 발생할 수 있습니다.
Q7: defer와 async 스크립트의 차이는?
둘 다 HTML 파싱과 병렬로 스크립트를 다운로드합니다. defer는 DOM 완성 후 스크립트 순서대로 실행하며, DOMContentLoaded 전에 실행됩니다. async는 다운로드 완료 즉시 실행하며 순서를 보장하지 않습니다.
Q8: GPU 가속이 적용되는 CSS 속성은?
transform, opacity, filter가 대표적입니다. 이 속성들은 합성(Composite) 단계에서만 처리되어 레이아웃이나 페인트를 건너뜁니다. will-change로 브라우저에 힌트를 줄 수 있습니다.
Q9: Web Worker의 용도와 제한사항은?
메인 스레드를 차단하지 않고 CPU 집약적 작업(정렬, 암호화, 이미지 처리)을 수행합니다. 제한사항으로 DOM에 직접 접근할 수 없으며, 메인 스레드와 postMessage로만 통신합니다.
Q10: Virtual DOM이 필요한 이유를 렌더링 파이프라인 관점에서 설명하세요.
직접 DOM을 여러 번 조작하면 각 변경마다 리플로우/리페인트가 발생합니다. Virtual DOM은 메모리에서 변경 사항을 비교(diff)한 후 최소한의 실제 DOM 업데이트를 수행하여 리플로우 횟수를 줄입니다.
퀴즈 5문제
Q1: 다음 코드의 콘솔 출력 순서는?
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
정답: A, D, C, B
동기 코드(A, D)가 먼저 실행되고, Microtask인 Promise(C)가 다음, Macrotask인 setTimeout(B)이 마지막입니다.
Q2: CSS에서 어떤 속성 변경이 합성(Composite)만 일어나는가?
정답: transform과 opacity입니다. 이 속성들은 GPU 합성 레이어에서 처리되어 Layout과 Paint 단계를 건너뜁니다. filter도 합성 레이어에서 처리됩니다.
Q3: preload와 prefetch의 차이는?
정답: preload는 현재 페이지에서 곧 필요한 중요 리소스를 높은 우선순위로 미리 로드합니다. prefetch는 다음 네비게이션에 필요할 것으로 예상되는 리소스를 낮은 우선순위로 미리 가져옵니다.
Q4: 레이아웃 트래싱(Layout Thrashing)이란?
정답: DOM 읽기(offsetWidth 등)와 쓰기(style 변경)를 교차하면, 브라우저가 정확한 값을 반환하기 위해 매번 강제 리플로우를 수행하는 현상입니다. 해결법은 읽기를 먼저 모아서 하고, 쓰기를 나중에 모아서 하는 배칭입니다.
Q5: display: none, visibility: hidden, opacity: 0의 차이를 렌더링 관점에서 설명하세요.
정답:
display: none: 렌더 트리에서 완전히 제거, 공간 없음, 토글 시 리플로우 발생visibility: hidden: 렌더 트리에 존재, 공간 차지, 리페인트만 발생opacity: 0: 렌더 트리에 존재, 공간 차지, 합성 레이어에서 처리 가능, 이벤트도 받음
참고 자료
- Google - How Browsers Work
- MDN - Critical Rendering Path
- Chrome - Inside Look at Modern Web Browser
- web.dev - Rendering Performance
- MDN - Event Loop
- web.dev - Optimize LCP
- Chrome - Compositing
- MDN - Web Workers API
- web.dev - requestAnimationFrame
- Chrome DevTools - Performance
- web.dev - Core Web Vitals
- Philip Roberts - Event Loop Talk
- CSS Triggers
How Browsers Work: From URL Input to Pixel Rendering — Essential Frontend Interview Knowledge
- Introduction
- 1. From URL Input to Screen Display (Complete Interview Answer)
- 2. Network Phase
- 3. HTML Parsing
- 4. CSS Parsing
- 5. Render Tree
- 6. Layout (Reflow)
- 7. Paint and Compositing
- 8. Critical Rendering Path Optimization
- 9. Reflow vs Repaint
- 10. Event Loop and Task Queue
- 11. Web Workers and OffscreenCanvas
- 12. DevTools Performance Panel Guide
- 13. Interview Questions and Quiz
- References
Introduction
"What happens when you type a URL in the browser address bar?"
This is one of the most frequently asked frontend interview questions, and it tests your deep understanding of browser internals. A simple answer like "it fetches HTML and shows it on screen" will not suffice.
This guide covers every stage of browser operation systematically -- from DNS lookup, TCP connection, HTML/CSS parsing, DOM/CSSOM construction, render tree generation, layout, paint, compositing, and the event loop.
1. From URL Input to Screen Display (Complete Interview Answer)
1.1 Complete Flow Summary
User: Types URL + Enter
|
v
1. URL Parsing (protocol, domain, path)
|
v
2. DNS Lookup (domain → IP address)
|
v
3. TCP Connection (3-way handshake)
|
v
4. TLS Handshake (for HTTPS)
|
v
5. HTTP Request/Response
|
v
6. HTML Parsing → DOM Tree
|
v
7. CSS Parsing → CSSOM Tree
|
v
8. DOM + CSSOM → Render Tree
|
v
9. Layout (calculate position/size of each element)
|
v
10. Paint (draw pixels)
|
v
11. Composite (merge layers via GPU)
|
v
Displayed on screen!
2. Network Phase
2.1 DNS Lookup (Domain Name System)
The browser converts domain names to IP addresses.
Lookup order:
1. Browser DNS cache
2. OS DNS cache
3. Local hosts file
4. DNS Resolver (ISP)
5. Root DNS server
6. TLD DNS server (.com, .org, etc.)
7. Authoritative DNS server (returns actual IP)
Example: www.example.com lookup
Browser Cache → miss
OS Cache → miss
Router Cache → miss
ISP DNS Resolver → miss
Root Server → ".com is handled over here"
.com TLD Server → "example.com is handled over here"
example.com NS → "IP is 93.184.216.34"
Optimization techniques:
dns-prefetch: Pre-resolve DNS ahead of time
<link rel="dns-prefetch" href="//api.example.com" />
<link rel="preconnect" href="https://cdn.example.com" />
2.2 TCP 3-Way Handshake
Client Server
| |
|--- SYN (seq=x) ------->|
| |
|<-- SYN-ACK (seq=y, -----|
| ack=x+1) |
| |
|--- ACK (ack=y+1) ----->|
| |
Connection established!
2.3 TLS Handshake
Additional encryption process for HTTPS connections.
Client Server
| |
|-- ClientHello (TLS version, -->|
| cipher suites) |
| |
|<-- ServerHello, Certificate, ---|
| Server Key Exchange |
| |
|-- Client Key Exchange, -->|
| ChangeCipherSpec, |
| Finished |
| |
|<-- ChangeCipherSpec, ---|
| Finished |
| |
Encrypted communication begins!
2.4 HTTP Request/Response
GET / HTTP/2
Host: www.example.com
Accept: text/html,application/xhtml+xml
Accept-Encoding: gzip, br
Connection: keep-alive
HTTP/2 200 OK
Content-Type: text/html; charset=UTF-8
Content-Encoding: gzip
Cache-Control: max-age=3600
Content-Length: 12345
<!DOCTYPE html>
<html>...
3. HTML Parsing
3.1 Tokenizer and Tree Construction
The HTML parser converts the byte stream into tokens, then builds DOM nodes.
Bytes → Characters → Tokens → Nodes → DOM Tree
Example HTML:
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1>Hello</h1>
<p>World</p>
</body>
</html>
DOM Tree:
Document
└─ html
├─ head
│ └─ title
│ └─ "My Page"
└─ body
├─ h1
│ └─ "Hello"
└─ p
└─ "World"
3.2 Script Blocking (Parser Blocking)
By default, encountering a script tag halts HTML parsing.
<!-- Parser-blocking: HTML parsing stops, script downloads + executes, then resumes -->
<script src="app.js"></script>
<!-- defer: Downloads parallel to parsing, executes after DOM is complete -->
<script defer src="app.js"></script>
<!-- async: Downloads parallel to parsing, executes immediately when downloaded -->
<script async src="analytics.js"></script>
defer vs async comparison:
Regular script:
HTML: ───████████████|████|████████
JS: |down|exec|
defer:
HTML: ───████████████████████████|exec|
JS: |████ download ████|
async:
HTML: ───████████|exec|████████████
JS: |████down|
defer: Preserves order, executes before DOMContentLoadedasync: No order guarantee, executes as soon as downloaded
3.3 Preload Scanner
Even when the parser is blocked, the preload scanner continues working to discover resources early.
<!-- preload: Pre-load critical resources -->
<link rel="preload" href="critical.css" as="style" />
<link rel="preload" href="hero.webp" as="image" />
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
<!-- prefetch: Fetch resources for next navigation -->
<link rel="prefetch" href="/next-page.html" />
4. CSS Parsing
4.1 CSSOM Construction
CSS goes through a similar process as HTML.
Bytes → Characters → Tokens → Nodes → CSSOM Tree
CSS:
body { font-size: 16px; }
h1 { color: blue; font-size: 2em; }
p { color: #333; }
CSSOM Tree:
body (font-size: 16px)
├─ h1 (color: blue, font-size: 32px)
└─ p (color: #333)
4.2 CSS is a Render-Blocking Resource
The render tree cannot be built until CSS is loaded, making CSS a render-blocking resource.
<!-- Render-blocking: Rendering blocked until this CSS is loaded/parsed -->
<link rel="stylesheet" href="styles.css" />
<!-- Conditional: Print CSS does not block rendering -->
<link rel="stylesheet" href="print.css" media="print" />
<!-- Conditional: Only render-blocks at specific viewport -->
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)" />
4.3 Specificity Calculation
!important > inline styles > ID > class/attribute/pseudo-class > element/pseudo-element
Calculation: (a, b, c)
- a: Number of ID selectors
- b: Number of class, attribute, pseudo-class selectors
- c: Number of element, pseudo-element selectors
Examples:
#header .nav a → (1, 1, 1)
.nav .item.active → (0, 3, 0)
nav ul li a → (0, 0, 4)
#main #content p.text → (2, 1, 1)
4.4 Cascade Rules
Priority (highest first):
1. User-agent styles with !important
2. User styles with !important
3. Author styles with !important
4. Author styles (by specificity)
5. User styles
6. User-agent styles (browser defaults)
5. Render Tree
5.1 DOM + CSSOM = Render Tree
DOM Tree: CSSOM:
html body { font: 16px }
├─ head h1 { color: blue }
│ └─ title p { color: #333 }
├─ body .hidden { display: none }
│ ├─ h1
│ ├─ p
│ └─ div.hidden
│ └─ span (visibility: hidden)
Render Tree (visible elements only):
html
└─ body (font: 16px)
├─ h1 (color: blue)
├─ p (color: #333)
└─ span (visibility: hidden) ← included! Takes up space
[div.hidden excluded - display: none]
5.2 display: none vs visibility: hidden
| Property | In Render Tree | Takes Space | Triggers Reflow |
|---|---|---|---|
display: none | No | No | On add/remove |
visibility: hidden | Yes | Yes | No |
opacity: 0 | Yes | Yes | No |
5.3 Render Tree Construction Process
- Traverse from the root of the DOM tree
- Skip invisible nodes (script, meta, display: none)
- Match CSSOM rules for each visible node
- Add node to render tree with computed styles
6. Layout (Reflow)
6.1 Box Model
Every element is represented as a Box Model.
┌─────────────────────────────────────┐
│ margin │
│ ┌───────────────────────────────┐ │
│ │ border │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ padding │ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ content │ │ │ │
│ │ │ │ (width x height)│ │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └───────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
box-sizing: content-box (default)
width = content width only
actual width = width + padding + border
box-sizing: border-box (recommended)
width = content + padding + border
actual width = width
6.2 Block vs Inline
Block elements (div, p, h1...):
┌──────────────────────────────┐
│ Block 1 (takes full width) │
├──────────────────────────────┤
│ Block 2 │
├──────────────────────────────┤
│ Block 3 │
└──────────────────────────────┘
Inline elements (span, a, strong...):
[inline1][inline2][inline3]
[inline4 wraps to next line...]
Inline-block:
[inline-block1] [inline-block2]
(can set width/height like block, flows inline)
6.3 Layout Process
- Start from root: Determine viewport size
- Block layout: Stack blocks top to bottom
- Inline layout: Flow inline elements left to right
- Float handling: Adjust text flow
- Position calculation: Process relative, absolute, fixed, sticky
- Finalize sizes: Calculate final position and size of all elements
7. Paint and Compositing
7.1 Paint
After layout, each element is drawn as actual pixels.
Paint order (z-order):
1. Background color
2. Background image
3. Border
4. Children
5. Outline
Painting is performed across multiple layers.
7.2 Layer Creation Conditions
New compositing layers are created when:
transform: translate3d()ortranslateZ()will-change: transformorwill-change: opacityposition: fixed- CSS animation/transition applied to opacity, transform
- Video, canvas elements
filterproperty used
/* Force compositing layer creation */
.layer {
will-change: transform;
/* or */
transform: translateZ(0);
}
7.3 Compositing
The GPU merges all layers into the final output.
Compositing steps:
1. Split into layers (Layer Tree)
2. Divide each layer into tiles
3. Rasterize tiles (convert to bitmaps on GPU)
4. Generate draw quads (screen position info)
5. Create compositor frame (final composite)
6. Display on screen via GPU
Rendering pipeline comparison:
JS/CSS change
→ Style → Layout → Paint → Composite (full pipeline)
→ Style → Paint → Composite (skip Layout)
→ Style → Composite (fastest!)
7.4 GPU-Accelerated Properties
/* Composite-only properties (fastest) */
.fast {
transform: translate(10px, 20px); /* position */
transform: scale(1.2); /* size */
transform: rotate(45deg); /* rotation */
opacity: 0.5; /* transparency */
}
/* Paint triggers (medium) */
.medium {
color: red;
background-color: blue;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
/* Layout triggers (slowest) */
.slow {
width: 200px;
height: 100px;
margin: 10px;
padding: 20px;
font-size: 18px;
}
8. Critical Rendering Path Optimization
8.1 Eliminate Render-Blocking Resources
<!-- CSS: Inline Critical CSS -->
<style>
/* Only minimum CSS needed for first screen */
body { margin: 0; font-family: system-ui; }
.hero { background: #3b82f6; color: white; padding: 2rem; }
</style>
<!-- Load remaining CSS asynchronously -->
<link rel="preload" href="styles.css" as="style"
onload="this.onload=null;this.rel='stylesheet'" />
<noscript><link rel="stylesheet" href="styles.css" /></noscript>
<!-- JS: Prevent parser blocking -->
<script defer src="app.js"></script>
8.2 Resource Hints
<!-- DNS pre-lookup -->
<link rel="dns-prefetch" href="//api.example.com" />
<!-- Pre-connect (DNS + TCP + TLS) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<!-- Preload critical resources -->
<link rel="preload" href="hero.webp" as="image" />
<link rel="preload" href="critical.js" as="script" />
<!-- Prefetch next navigation resources -->
<link rel="prefetch" href="/about" />
<!-- Pre-render entire next page -->
<link rel="prerender" href="/likely-next-page" />
8.3 Optimization Checklist
- Place CSS in head: Minimize render blocking
- Inline Critical CSS: Only CSS needed for first screen
- Use defer/async for JS: Prevent parser blocking
- Compress resources: gzip/brotli compression
- Optimize images: WebP/AVIF, lazy loading, srcset
- Optimize fonts: font-display: swap, preload
- Use HTTP/2: Multiplexing for parallel requests
9. Reflow vs Repaint
9.1 Reflow (Layout Recalculation)
An expensive operation that recalculates layout.
Reflow triggers:
- Adding/removing elements
- Changing element size (width, height, padding, margin, border)
- Font size changes
- Text content changes
- Window resize
- Reading computed styles (offsetWidth, getComputedStyle, etc.)
9.2 Repaint
A relatively cheaper operation that only changes visual properties.
Repaint-only triggers (no reflow):
- color, background-color
- visibility
- box-shadow
- outline
9.3 DOM Read/Write Batching
// Bad: Interleaved reads and writes cause forced reflow
const items = document.querySelectorAll('.item');
items.forEach(item => {
const width = item.offsetWidth; // Read → forced reflow!
item.style.width = width + 10 + 'px'; // Write
});
// Good: Batch all reads first, then all writes
const widths = [];
items.forEach(item => {
widths.push(item.offsetWidth); // All reads first
});
items.forEach((item, i) => {
item.style.width = widths[i] + 10 + 'px'; // All writes after
});
9.4 Why Virtual DOM Exists
Direct DOM manipulation:
Change 1 → Reflow → Repaint
Change 2 → Reflow → Repaint
Change 3 → Reflow → Repaint
Virtual DOM (React):
Calculate changes 1, 2, 3 in memory (diff)
→ Minimal actual DOM changes
→ 1 Reflow → 1 Repaint
10. Event Loop and Task Queue
10.1 JavaScript Runtime Structure
┌─────────────────────────┐
│ Call Stack │
│ (execution contexts) │
└───────────┬─────────────┘
│
┌───────────▼─────────────┐
│ Event Loop │
│ (checks queues when │
│ stack is empty) │
└───┬─────────────────┬───┘
│ │
┌───▼───┐ ┌────▼────┐
│Micro │ │ Macro │
│Task │ │ Task │
│Queue │ │ Queue │
└───────┘ └─────────┘
10.2 Macrotask vs Microtask
console.log('1: Sync');
setTimeout(() => {
console.log('2: Macrotask (setTimeout)');
}, 0);
Promise.resolve().then(() => {
console.log('3: Microtask (Promise)');
});
queueMicrotask(() => {
console.log('4: Microtask (queueMicrotask)');
});
console.log('5: Sync');
// Output order:
// 1: Sync
// 5: Sync
// 3: Microtask (Promise)
// 4: Microtask (queueMicrotask)
// 2: Macrotask (setTimeout)
Macrotask (Task Queue):
- setTimeout, setInterval
- I/O callbacks
- UI rendering
- requestAnimationFrame
Microtask (Microtask Queue):
- Promise.then/catch/finally
- queueMicrotask
- MutationObserver
10.3 Event Loop Execution Order
1. Execute all synchronous code in Call Stack
2. When Call Stack is empty:
a. Execute ALL tasks in Microtask Queue (until empty)
b. If rendering is needed, perform rendering
c. Take ONE task from Macrotask Queue and execute
3. Go back to step 2 and repeat
10.4 requestAnimationFrame (rAF)
// rAF: Executes callback right before next repaint
// Usually 60fps = ~16.67ms interval
function animate() {
// Animation logic
element.style.transform = `translateX(${position}px)`;
position += 2;
if (position < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
rAF vs setTimeout:
// Bad: setTimeout for animation
// Frame drops, inconsistent intervals
setInterval(() => {
moveElement();
}, 16);
// Good: rAF
// Synced with browser refresh, smooth 60fps
function animate() {
moveElement();
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
10.5 requestIdleCallback
// Executes when browser is idle
// Use for non-critical tasks
requestIdleCallback((deadline) => {
// Check remaining time with deadline.timeRemaining()
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
performTask(tasks.shift());
}
if (tasks.length > 0) {
requestIdleCallback(processRemainingTasks);
}
});
11. Web Workers and OffscreenCanvas
11.1 Web Worker
Execute JavaScript in the background without blocking the main thread.
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ type: 'HEAVY_CALC', data: bigArray });
worker.onmessage = (event) => {
console.log('Result:', event.data.result);
};
// worker.js
self.onmessage = (event) => {
if (event.data.type === 'HEAVY_CALC') {
const result = heavyComputation(event.data.data);
self.postMessage({ result });
}
};
function heavyComputation(data) {
// CPU-intensive work (sorting, encryption, image processing, etc.)
return data.sort((a, b) => a - b);
}
11.2 Worker Types
// 1. Dedicated Worker: 1:1 relationship
const dedicated = new Worker('worker.js');
// 2. Shared Worker: Shared across tabs/iframes
const shared = new SharedWorker('shared.js');
shared.port.start();
shared.port.postMessage('hello');
// 3. Service Worker: Network proxy, offline support
navigator.serviceWorker.register('/sw.js');
11.3 OffscreenCanvas
Perform canvas rendering in a Worker.
// main.js
const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('render-worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
// render-worker.js
self.onmessage = (event) => {
const canvas = event.data.canvas;
const ctx = canvas.getContext('2d');
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Complex rendering logic...
ctx.fillStyle = '#3b82f6';
ctx.fillRect(x, y, 50, 50);
requestAnimationFrame(draw);
}
draw();
};
12. DevTools Performance Panel Guide
12.1 Using the Performance Tab
1. Open DevTools (F12 or Cmd+Option+I)
2. Select Performance tab
3. Start recording (Ctrl+E)
4. Interact with page
5. Stop recording
Interpreting results:
- FPS chart: Green = 60fps, Red = frame drops
- CPU chart: Colors indicate work type
- Yellow: JavaScript execution
- Purple: Layout (reflow)
- Green: Paint
- Gray: Other
12.2 Key Metrics
Core Web Vitals:
- LCP (Largest Contentful Paint): under 2.5s → good
- INP (Interaction to Next Paint): under 200ms → good
- CLS (Cumulative Layout Shift): under 0.1 → good
Additional metrics:
- FCP (First Contentful Paint): first content rendered
- TTFB (Time to First Byte): server response time
- TBT (Total Blocking Time): main thread blocking
12.3 Common Performance Problem Patterns
1. Long Tasks:
- Main thread blocked for 50ms+
- Solution: Code splitting, Web Workers
2. Layout Thrashing:
- Forced reflows from interleaved reads/writes
- Solution: Batch reads/writes
3. Excessive Paint:
- Unnecessary area repaints
- Solution: will-change, use transform
4. Large DOM Tree:
- 1500+ nodes degrade performance
- Solution: Virtualization (react-virtualized, tanstack-virtual)
13. Interview Questions and Quiz
Top 10 Interview Questions
Q1: What happens when you type a URL in the browser address bar?
After URL parsing, DNS lookup resolves the IP address. TCP 3-way handshake establishes connection, plus TLS handshake for HTTPS. The HTTP response delivers HTML, which the parser builds into a DOM tree. CSS is parsed into CSSOM. DOM and CSSOM combine into a render tree, layout calculates position/size, paint draws pixels, and compositing merges layers via GPU for final display.
Q2: Explain Critical Rendering Path and how to optimize it.
CRP is the path from receiving HTML to rendering the first screen. CSS is render-blocking, so inline Critical CSS and defer non-critical CSS. Use defer/async on JS to prevent parser blocking. Preload critical resources and eliminate unnecessary CSS/JS.
Q3: What is the difference between Reflow and Repaint?
Reflow recalculates layout (position, size changes) and is expensive. Repaint only changes visual properties (color, shadow) and is relatively cheaper. Reflow always triggers repaint, but repaint can occur without reflow.
Q4: What is the difference between display: none and visibility: hidden?
display: none completely removes the element from the render tree -- it takes no space. Toggling triggers reflow. visibility: hidden keeps the element in the render tree, it takes up space, and only triggers repaint.
Q5: What is the execution order of Microtasks and Macrotasks in the Event Loop?
After synchronous code executes and the Call Stack empties, all Microtask Queue tasks are processed first. Once the Microtask Queue is empty, rendering occurs if needed, then one task from the Macrotask Queue executes. This repeats.
Q6: What is requestAnimationFrame and how does it differ from setTimeout?
rAF executes callbacks right before the next repaint, ensuring smooth 60fps animation. setTimeout does not guarantee exact timing and is not synced with the browser refresh rate, causing potential frame drops.
Q7: What is the difference between defer and async scripts?
Both download scripts in parallel with HTML parsing. defer executes after DOM completion in script order, before DOMContentLoaded. async executes immediately when downloaded with no order guarantee.
Q8: Which CSS properties benefit from GPU acceleration?
transform, opacity, and filter are the primary ones. These properties are handled at the Composite stage, skipping layout and paint. You can hint the browser with will-change.
Q9: What are Web Workers used for and what are their limitations?
Workers run CPU-intensive tasks (sorting, encryption, image processing) without blocking the main thread. Limitations include no direct DOM access and communication only through postMessage.
Q10: Explain why Virtual DOM is needed from a rendering pipeline perspective.
Directly manipulating DOM multiple times triggers reflow/repaint for each change. Virtual DOM diffs changes in memory, then performs minimal actual DOM updates, reducing the number of reflows.
5 Quiz Questions
Q1: What is the console output order of this code?
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
Answer: A, D, C, B
Synchronous code (A, D) runs first, then Microtask Promise (C), then Macrotask setTimeout (B).
Q2: Which CSS property changes trigger only Compositing?
Answer: transform and opacity. These are processed by the GPU composite layer, skipping Layout and Paint stages. filter is also handled at the composite layer.
Q3: What is the difference between preload and prefetch?
Answer: preload loads critical resources needed for the current page with high priority. prefetch fetches resources expected for the next navigation with low priority.
Q4: What is Layout Thrashing?
Answer: When DOM reads (offsetWidth, etc.) and writes (style changes) are interleaved, the browser must perform a forced reflow each time to return accurate values. The solution is to batch all reads first, then all writes.
Q5: Explain the rendering differences between display: none, visibility: hidden, and opacity: 0.
Answer:
display: none: Completely removed from render tree, no space, triggers reflow on togglevisibility: hidden: Stays in render tree, takes space, triggers repaint onlyopacity: 0: Stays in render tree, takes space, can be handled by composite layer, still receives events
References
- Google - How Browsers Work
- MDN - Critical Rendering Path
- Chrome - Inside Look at Modern Web Browser
- web.dev - Rendering Performance
- MDN - Event Loop
- web.dev - Optimize LCP
- Chrome - Compositing
- MDN - Web Workers API
- web.dev - requestAnimationFrame
- Chrome DevTools - Performance
- web.dev - Core Web Vitals
- Philip Roberts - Event Loop Talk
- CSS Triggers