자바스크립트의 편리함 뒤에 숨겨진 비용

크리스는 자료구조 중 하나인 '큐(Queue)'를 구현하고 있었다.
큐는 선착순 대기열처럼 먼저 들어온 데이터가 먼저 나가는(FIFO) 구조다.
"자바스크립트 배열은 최고야. 그냥 넣을 때 push하고 뺄 때 shift하면 끝이네!"
// ❌ 크리스의 큐 구현
const queue = [];
// 데이터 10만 개 넣기 (Enqueue)
for (let i = 0; i < 100000; i++) {
queue.push(i);
}
// 데이터 10만 개 빼기 (Dequeue)
while (queue.length > 0) {
queue.shift(); // 앞에서 하나씩 뺌
}그런데 이상하다. push로 데이터를 넣을 때는 눈 깜짝할 새 끝났는데, shift로 데이터를 뺄 때는 시간이 엄청나게 오래 걸렸다.
둘 다 배열 메서드 한 줄을 썼을 뿐인데, 왜 속도 차이가 하늘과 땅 차이일까?
이유는 메서드마다 "비용(Cost)"이 다르기 때문이다. 오늘은 우리가 숨 쉬듯 사용하는 자바스크립트 배열 메서드들이 내부적으로 어떻게 동작하는지, 그 '비용'을 파헤쳐 본다.
1. 맨 뒤에서 노는 건 빠르다: push & pop
자바스크립트 배열은 메모리 상에 순서대로 나열되어 있다.
이 작업들은 기존에 줄 서 있는 데이터들을 건드릴 필요가 없다. 그냥 맨 뒤에 의자 하나 놓거나 빼면 끝이다.
데이터가 1개든 100만 개든, 이 작업에 걸리는 시간은 거의 같다. 이를 상수 시간(O(1))이라고 한다.
2. 맨 앞을 건드리면 지옥이 열린다: shift & unshift & splice
문제는 배열의 맨 앞이나 중간을 건드릴 때 발생한다.
크리스가 queue.shift()를 호출했을 때, 자바스크립트 엔진 내부에서는 이런 일이 벌어진다.
즉, 맨 앞의 하나를 지우기 위해 나머지 모든 데이터를 한 칸씩 앞으로 이동(Re-indexing)시켜야 한다.
데이터가 N개라면 N번의 이동 작업이 필요하다. 이를 선형 시간(O(N))이라고 한다. 크리스의 코드는 10만 번 shift를 호출했으니, 내부적으로는 10만 × 10만(대략) 번의 이동 연산이 발생한 것이다. 느릴 수밖에 없다.
3. 마법이 아니다: filter, map, slice, reduce
"그럼 반복문을 안 쓰고 filter나 map을 쓰면 빠르지 않을까요?"
많은 초보자가 하는 착각이다.
// 짝수만 남기기
const evens = numbers.filter(num => num % 2 === 0);이 코드는 매우 짧지만, 내부적으로는 결국 배열의 0번부터 끝까지 한 바퀴를 도는 반복문이다.
만약 이 코드를 또 다른 반복문 안에 넣는다면? 바로 성능 저하의 주범이 된다. slice, reduce, forEach 모두 마찬가지다. 이들은 문법적 설탕(Syntactic Sugar)일 뿐, 마법의 지팡이가 아니다.
4. 자바스크립트 배열 메서드 비용표 (Cheatsheet)
알고리즘 문제를 풀거나 성능 최적화를 할 때, 이 표를 항상 머릿속에 넣어두자.
| 메서드 | 하는 일 | 비용 (시간 복잡도) | 설명 |
| push | 맨 뒤에 추가 | O(1) (빠름) | 기존 인덱스 건드리지 않음 |
| pop | 맨 뒤 삭제 | O(1) (빠름) | 기존 인덱스 건드리지 않음 |
| shift | 맨 앞 삭제 | O(N) (느림) | 전체 요소 앞으로 한 칸씩 이동 |
| unshift | 맨 앞 추가 | O(N) (느림) | 전체 요소 뒤로 한 칸씩 이동 |
| splice | 중간 추가/삭제 | O(N) (느림) | 변경된 위치 뒤의 요소들 이동 |
| sort | 정렬 | O(N log N) | 가장 느림 (비용이 큼) |
| filter/map | 순회 | O(N) | 전체를 한 번 훑음 |
| arr[i] | 값 조회 | O(1) (빠름) | 인덱스로 바로 접근 |
5. 결론: 무엇을 써야 할까?
크리스가 짠 큐(Queue) 코드가 느렸던 이유는 자료구조의 특성을 무시하고 비용이 비싼 shift를 남발했기 때문이다.
(데이터가 많을 때는 배열 대신 연결 리스트(Linked List)를 직접 구현하거나, shift를 쓰지 않는 방식으로 구현해야 한다.)
"코드가 짧다고 해서 빠른 것이 아니다."
개발자는 함수 한 줄을 적을 때도, 그 함수가 내부적으로 몇 번의 연산을 수행할지 상상할 수 있어야 한다.
이 "몇 번의 연산"을 수학적으로 멋지게 표현하는 방법이 있다. 개발자들의 공용어, 빅오(Big-O) 표기법이다.
다음 글에서 O(1), O(N) 같은 기호들이 정확히 무엇을 의미하는지 알아보자.
"[Algorithm] 빅오 표현법과 시간 복잡도: 알고리즘의 성능 성적표" 편에서 계속된다.