자바스크립트는 싱글 스레드 언어임에도 불구하고 비동기 처리를 훌륭하게 수행할 수 있습니다. 그 비결은 바로 '이벤트 루프(Event Loop)'라는 구조 덕분입니다. 이벤트 루프는 자바스크립트의 실행 흐름을 제어하는 핵심 엔진으로, 동시에 여러 작업을 처리하는 것처럼 보이게 만드는 비결입니다. 특히 프론트엔드 개발자라면 이 구조를 제대로 이해해야 UI 응답성과 성능을 높일 수 있으며, 면접에서도 이 개념은 자주 질문되는 핵심 주제 중 하나입니다. 이 글에서는 이벤트 루프의 기본 개념부터, 태스크 큐와 마이크로태스크의 동작 방식 및 실행 우선순위까지 실전 중심으로 정리합니다.
자바스크립트 이벤트 루프 구조 완벽 이해
자바스크립트는 싱글 스레드 기반 언어로, 한 번에 한 작업만 수행할 수 있습니다. 그렇다면 어떻게 사용자 이벤트, 네트워크 요청, 타이머 같은 작업을 동시에 처리하는 것처럼 동작할 수 있을까요? 그 해답이 바로 이벤트 루프입니다.
이벤트 루프는 자바스크립트의 런타임 환경(브라우저 또는 Node.js)에서 비동기 작업을 처리하는 방식입니다. 자바스크립트의 실행 흐름은 크게 다음과 같은 순서를 따릅니다:
- 코드는 Call Stack(호출 스택)에 쌓여 동기적으로 실행됨
- setTimeout, fetch 같은 비동기 함수는 Web API 영역으로 위임됨
- 작업 완료 후 콜백은 태스크 큐(Task Queue)나 마이크로태스크 큐로 이동
- 이벤트 루프는 Call Stack이 비면 큐에서 하나씩 작업을 실행
즉, 이벤트 루프는 Call Stack이 비어 있는지 계속 감시하고 있다가, 비워지는 순간 큐에 있는 작업을 스택으로 옮겨 실행하는 메커니즘입니다.
이벤트 루프는 무한 반복되는 구조로 작동하며, 이를 통해 자바스크립트는 UI 반응을 멈추지 않으면서 백그라운드 작업을 처리할 수 있게 됩니다. 중요한 점은, 이 구조 안에서 "어떤 작업이 먼저 실행되느냐"가 마이크로태스크와 태스크 큐의 우선순위에 따라 달라진다는 것입니다. 이 차이를 명확히 이해하는 것이 실무 최적화와 면접 대응 모두에서 핵심입니다.
태스크 큐(Task Queue)와 메시지 처리 과정
태스크 큐는 비동기 작업의 콜백이 대기하는 기본적인 큐입니다. 예를 들어 setTimeout
, setInterval
, DOM 이벤트
, fetch
콜백 등이 해당됩니다. 브라우저는 이러한 작업이 완료되면 콜백 함수를 태스크 큐에 넣습니다.
중요한 점은, 이벤트 루프는 Call Stack이 비어야만 태스크 큐에서 작업을 하나씩 가져올 수 있다는 것입니다. 이 구조 덕분에 자바스크립트는 동기 코드가 먼저 실행되고, 비동기 콜백은 나중에 처리됩니다.
예제를 통해 이해해보겠습니다:
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
console.log("End");
// 출력 순서: Start → End → Timeout
비록 setTimeout
이 0ms로 설정되어 있어도, 브라우저는 이 콜백을 바로 실행하지 않습니다. 대신 태스크 큐에 넣고, 현재 Call Stack이 비워질 때까지 대기합니다. 따라서 "End"
가 먼저 출력되고 그 다음에 "Timeout"
이 실행됩니다.
태스크 큐는 FIFO(First In First Out) 방식으로 동작하며, 마이크로태스크보다 실행 우선순위가 낮습니다. 따라서 마이크로태스크 큐에 작업이 있으면 태스크 큐의 작업은 더 늦게 실행됩니다. 이 구조가 면접에서도 자주 물어보는 포인트이며, setTimeout과 Promise 콜백의 실행 순서를 비교하는 문제가 자주 출제됩니다.
마이크로태스크(Microtask)의 실행 우선순위
마이크로태스크 큐는 태스크 큐보다 더 높은 우선순위를 가진 큐입니다. 이 큐에는 Promise.then()
, catch()
, finally()
, queueMicrotask()
등의 콜백이 들어갑니다.
마이크로태스크는 이벤트 루프의 한 사이클이 끝날 때마다, 즉 Call Stack이 비고 나면 태스크 큐를 확인하기 전에 먼저 모두 실행됩니다. 이 구조는 자바스크립트의 비동기 처리에서 매우 중요한 동작 원리를 설명합니다.
아래 예제를 보겠습니다:
console.log("A");
Promise.resolve().then(() => {
console.log("B");
});
setTimeout(() => {
console.log("C");
}, 0);
console.log("D");
// 출력 순서: A → D → B → C
이 예제에서 Promise.then()
은 마이크로태스크 큐에 들어가고, setTimeout
은 태스크 큐에 들어갑니다. 이벤트 루프는 먼저 마이크로태스크 큐의 "B"
를 처리한 후, 그 다음에 태스크 큐의 "C"
를 처리합니다.
이 때문에 마이크로태스크는 “현재 실행 중인 작업이 끝나면 즉시 실행된다”는 개념으로 이해하는 것이 좋습니다. 이를 활용하면 비동기 흐름을 보다 정밀하게 제어할 수 있으며, 실무에서도 상태 동기화, 렌더링 타이밍 최적화에 활용됩니다.
면접에서는 종종 다음과 같은 질문이 나옵니다:
- “Promise와 setTimeout의 실행 순서는 왜 다르죠?”
- “마이크로태스크가 무한 루프를 만들 수 있나요?”
이러한 질문에 명확히 답하기 위해서는 이벤트 루프의 사이클, 두 큐의 실행 시점, 자바스크립트의 단일 스레드 구조까지 통합적으로 설명할 수 있어야 합니다. 마이크로태스크 큐를 활용한 예외 처리나 상태 관리 기법에 대해서도 이야기할 수 있다면 면접에서 높은 점수를 받을 수 있습니다.
이벤트 루프는 자바스크립트 엔진의 가장 핵심적인 구조 중 하나로, 동기와 비동기 처리를 연결하는 중심 역할을 합니다. 태스크 큐와 마이크로태스크 큐의 실행 우선순위는 단순한 개념 같지만, 실제 실행 순서 문제에서 많은 개발자들이 실수하기 쉽습니다. 따라서 단순 정의 암기보다 예제와 함께 실행 흐름을 눈으로 직접 확인하고, 각 큐가 언제 실행되는지를 정확히 이해해야 합니다. 면접에서는 반드시 “왜 그렇게 실행되는가”를 설명할 수 있어야 좋은 평가를 받을 수 있습니다. 이벤트 루프의 구조를 명확히 이해하면, 비동기 로직 처리와 성능 최적화는 물론, React나 Vue 같은 프레임워크의 동작 방식도 더 깊이 있게 이해할 수 있습니다.