큐 자료 구조란? FIFO 개념, 예제, 활용처 정리
Dev
마지막 업데이트

큐 자료 구조란? FIFO 개념, 예제, 활용처 정리


큐 자료 구조가 헷갈릴 때는 “정의”보다 “언제 큐를 써야 하는가”부터 먼저 잡는 편이 더 쉽습니다.

큐는 먼저 들어온 데이터를 먼저 처리하는 FIFO 구조이고, 작업 대기열, BFS, 메시지 처리처럼 순서 보장이 중요한 문제에서 자주 등장합니다.

이 글에서는 아래를 빠르게 정리합니다.

  • 큐가 정확히 어떤 문제를 해결하는지
  • 핵심 연산과 시간 복잡도를 어떻게 이해하면 좋은지
  • BFS와 작업 대기열 예제를 어떻게 읽어야 하는지
  • 배열 기반 큐가 언제 한계를 드러내는지

짧게 말하면 큐는 도착 순서가 곧 처리 규칙인 문제에 가장 잘 맞는 자료 구조입니다.


큐 자료 구조란 무엇인가?

큐는 FIFO(First In, First Out) 구조입니다.

먼저 들어온 데이터가 먼저 나갑니다. 은행 번호표 대기줄, 프린터 출력 순서, 콜센터 대기 순서처럼 “도착 순서”가 중요할 때 자연스럽게 떠올릴 수 있습니다.

큐를 떠올릴 때 핵심은 저장 방식보다 처리 규칙입니다.

  • 먼저 들어온 요청을 뒤로 미루면 안 되는가?
  • 순서를 보장해야 하는가?
  • 작업이 생산자와 소비자로 나뉘어 있는가?

이 질문에 “그렇다”가 많다면 큐가 후보가 됩니다.


큐는 언제 사용하는가?

1. 작업이 도착한 순서대로 처리되어야 할 때

예를 들어 주문 생성, 로그 적재, 이메일 발송 예약 같은 작업은 “먼저 들어온 요청이 먼저 처리되는 것”이 자연스러운 경우가 많습니다.

2. 생산자와 소비자가 분리된 비동기 시스템

웹 서버가 작업을 넣고, 백그라운드 워커가 하나씩 꺼내 처리하는 패턴은 큐를 이해하면 바로 감이 옵니다.

3. 알고리즘이 발견 순서를 보장해야 할 때

BFS는 가장 대표적인 예제입니다. 먼저 발견한 노드를 먼저 방문해야 하므로 큐가 거의 정답처럼 따라옵니다.

4. 버퍼링이 필요한 경우

생산 속도와 소비 속도가 다를 때, 큐는 두 흐름 사이의 완충 장치 역할도 합니다.


큐의 핵심 연산은 무엇인가?

큐를 설명할 때 보통 아래 세 연산이 함께 나옵니다.

  • enqueue: 데이터를 뒤에 넣기
  • dequeue: 앞에서 하나 꺼내기
  • peek: 맨 앞 데이터를 확인하기

실제로는 여기에 아래도 자주 붙습니다.

  • isEmpty: 큐가 비었는지 확인
  • size: 현재 몇 개가 들어 있는지 확인

시간 복잡도 감각

개념적으로는 큐의 앞/뒤 접근이 빠른 구조를 원합니다.

다만 여기서 중요한 포인트가 있습니다.

  • 추상 자료 구조로서의 큐 연산
  • 특정 언어의 배열 구현

은 구분해야 합니다.

예를 들어 JavaScript 배열의 push()는 보통 빠르지만, shift()는 앞 요소를 제거하면서 나머지를 당겨야 해서 큰 데이터에서는 비용이 커질 수 있습니다.

즉, “큐는 빠르다”가 아니라 큐에 맞는 구현을 골라야 빠르다가 더 정확합니다.


큐는 어떻게 구현할까?

개념을 익히기 위한 가장 쉬운 출발점은 배열 기반 구현입니다.

class Queue {
  constructor() {
    this.items = [];
  }

  enqueue(item) {
    this.items.push(item);
  }

  dequeue() {
    if (this.items.length === 0) return undefined;
    return this.items.shift();
  }

  peek() {
    return this.items[0];
  }

  isEmpty() {
    return this.items.length === 0;
  }

  size() {
    return this.items.length;
  }
}

이 구현은 학습용으로 충분히 좋습니다.

const queue = new Queue();

queue.enqueue('task-1');
queue.enqueue('task-2');
queue.enqueue('task-3');

console.log(queue.dequeue()); // task-1
console.log(queue.peek());    // task-2
console.log(queue.size());    // 2

여기서 중요한 관찰은:

  • 먼저 넣은 task-1이 먼저 나감
  • 새 작업은 항상 뒤로 붙음
  • 현재 처리할 다음 작업은 항상 맨 앞에 있음

입니다.


큐 예제 1: 작업 대기열은 어떻게 동작할까?

실무에서 큐를 가장 쉽게 이해하는 예제는 작업 대기열입니다.

최근 상태를 되돌리는 흐름과 비교하고 싶다면 여기서 잠깐 스택 자료 구조 가이드를 같이 떠올리면 좋습니다. 큐는 오래 기다린 작업을 먼저 처리하고, 스택은 가장 최근 상태를 먼저 꺼냅니다.

예를 들어 서버가 이미지를 업로드받고, 실제 썸네일 생성은 워커가 나중에 처리한다고 가정해 보겠습니다.

const jobQueue = [];

function submitJob(job) {
  jobQueue.push(job);
  console.log('queued:', job.id);
}

function processNextJob() {
  if (jobQueue.length === 0) {
    console.log('no jobs');
    return;
  }

  const job = jobQueue.shift();
  console.log('processing:', job.id);
}

submitJob({ id: 'img-101', type: 'thumbnail' });
submitJob({ id: 'img-102', type: 'thumbnail' });
submitJob({ id: 'img-103', type: 'thumbnail' });

processNextJob(); // img-101
processNextJob(); // img-102

이 패턴에서 큐가 중요한 이유는 공정성 때문입니다.

뒤에 들어온 작업이 앞 작업을 계속 추월하면, 오래 기다린 작업이 굶는 문제가 생길 수 있습니다.

물론 실무에서는 항상 FIFO만 쓰는 것은 아닙니다.

  • VIP 작업을 먼저 처리해야 할 수도 있고
  • 실패 재시도 작업에 별도 우선순위를 줄 수도 있고
  • 예약 시각 기반으로 처리해야 할 수도 있습니다.

그때는 우선순위 큐나 소티드 셋 같은 다른 구조를 검토합니다.

즉, 큐는 “순서를 단순하고 공정하게 유지하는 기본값” 이라고 보면 좋습니다.


큐 예제 2: BFS에서 큐를 왜 사용할까?

BFS는 큐를 왜 써야 하는지 가장 명확하게 보여 주는 알고리즘 예제입니다.

같은 그래프 탐색이라도 깊게 들어가는 방식이 궁금하다면 스택 자료 구조 가이드의 DFS 예제와 대비해서 보면 훨씬 잘 들어옵니다.

function bfs(graph, start) {
  const visited = new Set([start]);
  const queue = [start];

  while (queue.length > 0) {
    const node = queue.shift();
    console.log('visit:', node);

    for (const next of graph[node]) {
      if (!visited.has(next)) {
        visited.add(next);
        queue.push(next);
      }
    }
  }
}

const graph = {
  A: ['B', 'C'],
  B: ['D', 'E'],
  C: ['F'],
  D: [],
  E: [],
  F: []
};

bfs(graph, 'A');

방문 흐름은 아래처럼 됩니다.

  1. A 방문
  2. B, C를 큐 뒤에 넣음
  3. B를 먼저 꺼냄
  4. 그 다음 C를 꺼냄

이 구조 덕분에 BFS는 같은 거리의 노드를 먼저 처리하고, 더 먼 노드는 나중에 처리할 수 있습니다.

스택을 쓰면 DFS처럼 전혀 다른 방문 순서가 나오기 때문에, BFS와 큐는 거의 같이 기억하는 편이 좋습니다.


배열로 구현한 큐는 언제 한계가 생길까?

학습 단계에서는 배열 큐가 좋지만, 실제 서비스에서는 몇 가지 문제가 생깁니다.

1. 앞에서 빼는 연산이 비싸질 수 있음

JavaScript의 shift()처럼 구현상 비용이 커지는 연산은 큐 길이가 커질수록 부담이 됩니다.

2. 메모리와 성능 특성을 세밀하게 제어하기 어려움

고성능 시스템에서는 연결 리스트, 덱(deque), 원형 버퍼가 더 적합할 수 있습니다.

3. 멀티 프로듀서/멀티 컨슈머 환경은 더 복잡함

스레드 안전성, 락, 백프레셔, 재시도 정책까지 생각해야 하므로 단순 배열을 넘어서게 됩니다.

실무에서의 선택지는 대략 이렇습니다.

  • 애플리케이션 내부 소규모 버퍼: 배열 또는 덱
  • 고성능 런타임 내부 구조: 원형 버퍼, 락 프리 큐
  • 서비스 간 비동기 작업: RabbitMQ, Kafka, SQS 같은 메시지 시스템

큐는 다른 자료 구조와 무엇이 다를까?

큐 vs 스택

  • 큐: 먼저 들어온 것이 먼저 나감
  • 스택: 나중에 들어온 것이 먼저 나감

작업 처리 순서가 중요하면 큐, 최근 상태 복원이 중요하면 스택입니다.

큐 vs 우선순위 큐

  • 큐: 도착 순서가 기준
  • 우선순위 큐: 우선순위 값이 기준

도착 순서의 공정성이 중요하면 큐, 급한 작업을 먼저 처리해야 하면 우선순위 큐가 더 잘 맞습니다.

큐 vs 소티드 셋

  • 큐: 맨 앞 하나를 순서대로 소비
  • 소티드 셋: 정렬된 전체 집합과 범위 조회까지 활용

랭킹, 시간 기반 예약, 상위 10개 조회는 소티드 셋이 더 자연스럽습니다.

이 지점은 소티드 셋 가이드에서 리더보드와 예약 작업 예제로 더 자세히 이어집니다.


큐를 공부할 때 자주 하는 실수는?

1. 큐가 필요한데 정렬로 해결하려는 경우

도착 순서 자체가 규칙이라면 정렬보다 큐가 먼저 떠올라야 합니다.

2. 작은 예제로만 보고 성능을 일반화하는 경우

배열로 만든 큐가 잘 동작한다고 해서, 큰 트래픽에서도 그대로 적합하다고 보면 위험합니다.

3. 큐와 메시지 브로커를 같은 것으로 보는 경우

메시지 브로커는 큐 개념을 활용한 시스템일 수 있지만, 네트워크, 내구성, 재시도, ack 정책까지 포함하는 더 큰 개념입니다.

4. BFS와 큐를 따로 외우는 경우

큐를 써야 BFS가 자연스럽게 구현된다는 연결을 같이 잡아야 오래 기억됩니다.


어떤 문제에서 큐를 먼저 떠올리면 좋을까?

아래 질문에 “예”가 많다면 큐를 먼저 검토해도 좋습니다.

  1. 들어온 순서를 되도록 그대로 유지해야 하는가?
  2. 생산과 소비가 시간적으로 분리되는가?
  3. 다음에 처리할 일은 항상 가장 오래 기다린 일인가?
  4. 특정 우선순위보다 공정한 순차 처리가 더 중요한가?

FAQ

Q. 큐는 배열로 구현해도 되나요?

작은 규모나 학습용이라면 충분합니다. 다만 언어별 연산 비용을 이해하고, 트래픽이 커지면 다른 구현으로 바꿀 수 있어야 합니다.

Q. 큐와 덱은 어떻게 다른가요?

큐는 보통 한쪽에서 넣고 반대쪽에서 빼는 모델이고, 덱은 양쪽 삽입과 삭제가 모두 가능한 구조입니다.

Q. 실무에서 큐를 어디서 제일 자주 보나요?

작업 대기열, 메시지 처리, 이벤트 소비, BFS형 탐색, 버퍼링이 필요한 흐름에서 자주 봅니다.


먼저 읽어볼 가이드

검색 유입이 많은 핵심 글부터 이어서 보세요.