Maximum call stack size exceeded 오류가 발생하는 이유와 해결법
자바스크립트 코드를 실행하다가 RangeError: Maximum call stack size exceeded라는 오류를 만나면 코드가 갑자기 멈추면서 어디서 문제가 시작됐는지 찾기 어려울 수 있습니다. 특히 재귀 함수나 이벤트 처리처럼 함수가 연속해서 호출되는 코드에서 자주 볼 수 있는데요.
Maximum call stack size exceeded 오류는 쉽게 말해 함수 호출이 너무 많이 쌓여 자바스크립트가 더 이상 처리할 수 없는 상태가 됐다는 의미입니다.
Call Stack이 무엇인지부터 무한 재귀가 발생하는 이유, 자주 놓치는 원인과 해결 순서까지 쉽게 알아보겠습니다.
Maximum call stack size exceeded 뜻은?
자바스크립트는 함수를 실행할 때 Call Stack(호출 스택)이라는 구조를 이용해 현재 어떤 함수가 실행 중인지 관리합니다. 함수가 호출되면 스택에 작업이 추가되고 실행을 마치면 제거되는 방식입니다.
function first() {
second();
}
function second() {
console.log("실행");
}
first();
정상적인 코드라면 함수 실행이 끝나면서 스택도 차례대로 비워집니다. 반대로 함수 호출이 끝나지 않은 상태에서 계속 새로운 호출이 추가되면 스택이 지나치게 커지고 결국 Maximum call stack size exceeded가 발생할 수 있습니다.
Maximum call stack 원인 1, 무한 재귀 함수
가장 대표적인 원인은 종료되지 않는 재귀 함수입니다. 재귀 함수란 함수 내부에서 자기 자신을 다시 호출하는 함수를 말합니다.
function hello() {
hello();
}
hello();
위 코드는 hello()가 실행될 때마다 다시 hello()를 호출합니다. 끝나는 조건이 없기 때문에 함수 호출이 계속 쌓이고 결국 호출 스택의 한계를 넘게 됩니다.
재귀 자체가 잘못된 것은 아닙니다. 핵심은 반드시 종료 조건(base case)이 있어야 한다는 것입니다.
function countdown(number) {
if (number <= 0) {
return;
}
console.log(number);
countdown(number - 1);
}
countdown(5);
재귀 종료 조건이 있어도 오류가 나는 이유
1. 종료 조건에 절대 도달하지 않는 경우
조건을 작성했더라도 재귀 호출에서 값이 잘못 변하면 종료 조건까지 도달하지 못합니다.
// 잘못된 예
function countdown(number) {
if (number === 0) return;
countdown(number + 1);
}
countdown(5);
0에 도달해야 종료되는데 숫자가 계속 증가하므로 끝나지 않습니다. 재귀 함수를 확인할 때는 종료 조건뿐 아니라 매번 값이 종료 조건을 향해 변하고 있는지까지 살펴봐야 합니다.
2. 입력 데이터가 예상보다 너무 큰 경우
논리적으로는 종료되는 재귀 함수라도 호출 깊이가 지나치게 깊으면 실행 환경의 스택 한계에 도달할 수 있습니다. 이런 경우 반복문으로 변경하거나 알고리즘 구조 자체를 개선할 필요가 있습니다.
서로 다른 함수가 반복 호출되는 경우
자기 자신을 직접 호출하지 않아도 오류가 발생할 수 있습니다. 함수 A가 B를 호출하고 B가 다시 A를 호출하는 간접 재귀도 확인해야 합니다.
function a() {
b();
}
function b() {
a();
}
a();
코드가 여러 파일이나 함수로 나뉘어 있다면 이런 구조를 눈으로 발견하기 어려울 수 있습니다. Stack Trace에 같은 함수 이름들이 반복적으로 나타나는지 확인하면 중요한 단서를 얻을 수 있습니다.
getter와 setter에서도 발생할 수 있어요
객체의 getter나 setter를 사용할 때 자기 자신과 같은 속성을 다시 읽거나 수정하면서 반복 호출이 생기는 경우도 있습니다.
class User {
set name(value) {
this.name = value;
}
}
const user = new User();
user.name = "민수";
name setter 안에서 다시 this.name에 값을 넣으므로 같은 setter가 계속 실행될 수 있습니다. 이런 경우 실제 값을 저장하는 별도의 내부 속성을 사용할 수 있습니다.
class User {
set name(value) {
this._name = value;
}
get name() {
return this._name;
}
}
이벤트와 상태 업데이트도 확인하세요
이벤트 핸들러가 다른 이벤트를 실행하고 그 이벤트가 다시 처음 핸들러를 호출하는 구조에서도 반복 호출이 발생할 수 있습니다. 프레임워크를 사용하는 경우 렌더링이나 상태 변경 로직이 서로를 계속 실행시키는 구조가 없는지도 확인해야 합니다.
이때 단순히 오류가 발생한 마지막 함수만 보는 것보다 어떤 호출이 다음 호출을 만들었는지 추적하는 것이 중요합니다. 브라우저 개발자 도구의 디버거와 Stack Trace를 활용하면 반복되는 호출 패턴을 찾는 데 도움이 됩니다.
Maximum call stack size exceeded 주요 원인 7가지
1. 재귀 함수에 종료 조건이 없는 경우
2. 종료 조건은 있지만 값이 조건에 가까워지지 않는 경우
3. 재귀 호출 깊이가 지나치게 큰 경우
4. 두 개 이상의 함수가 서로 반복 호출하는 경우
5. getter 또는 setter가 자기 속성을 다시 호출하는 경우
6. 이벤트 핸들러가 서로를 반복 실행하는 경우
7. 상태 변경이나 콜백 구조에서 의도하지 않은 호출 반복이 발생하는 경우
결국 공통점은 함수 호출이 정상적으로 끝나기 전에 새로운 함수 호출이 계속 추가된다는 것입니다. 따라서 어떤 함수가 반복되고 있는지를 찾는 것이 해결의 출발점입니다.
Maximum call stack 오류 해결 순서
먼저 콘솔에 표시되는 Stack Trace를 확인하세요. 동일한 함수명이 수십 번 반복된다면 해당 함수가 자기 자신을 계속 호출하고 있을 가능성이 높습니다.
그다음 해당 함수의 종료 조건과 호출 부분을 확인합니다. 재귀가 아니라면 서로 다른 함수, getter/setter, 이벤트 또는 상태 변경 로직 사이에 순환 구조가 없는지 살펴보세요.
| 순서 | 확인할 내용 |
| 1단계 | 콘솔의 Stack Trace 확인 |
| 2단계 | 반복되는 함수 이름 찾기 |
| 3단계 | 재귀 함수의 종료 조건 확인 |
| 4단계 | 값이 종료 조건 방향으로 변하는지 확인 |
| 5단계 | 함수 간 순환 호출 여부 확인 |
| 6단계 | getter·setter·이벤트 호출 구조 확인 |
| 7단계 | 필요하면 재귀를 반복문 등으로 변경 |
setTimeout으로 바꾸면 해결될까?
검색하다 보면 재귀 호출을 setTimeout()으로 감싸면 오류가 사라지는 사례를 볼 수 있습니다. 실행 방식이 달라져 현재 호출 스택이 계속 쌓이는 문제를 피할 수 있는 상황은 있지만, 이것이 모든 경우의 근본적인 해결책은 아닙니다.
원래 종료되어야 하는 코드가 무한 반복되고 있다면 먼저 반복 호출 자체의 논리 오류를 수정해야 합니다. 단순히 실행 시점만 미루면 프로그램이 계속 동작하면서 다른 성능 문제를 만들 수도 있습니다.
Maximum call stack 오류 예방 방법
재귀 함수를 작성할 때는 가장 먼저 종료 조건을 만들고, 입력값이 호출될 때마다 실제로 종료 조건에 가까워지는지 확인하는 습관이 중요합니다. 데이터 규모가 커질 가능성이 있다면 반복문 등 다른 구현 방법이 더 적절한지도 검토해 보세요.
또한 getter와 setter, 이벤트, 콜백처럼 코드가 다른 코드를 자동으로 실행하는 구조에서는 순환 호출이 생기지 않는지 확인하는 것이 좋습니다. 함수 호출 관계가 복잡하다면 디버거의 Call Stack을 이용해 실행 흐름을 단계별로 추적해 보세요.
Maximum call stack size exceeded는 쉽게 말해 “함수 호출이 너무 많이 쌓여 호출 스택의 한계를 넘었다”는 의미입니다. 가장 먼저 무한 재귀와 종료 조건을 확인하고, 문제가 없다면 함수 간 순환 호출, getter/setter, 이벤트와 상태 업데이트 구조를 차례대로 살펴보세요.
만약 Stack Trace에 같은 함수가 계속 반복된다면 문제를 찾을 수 있는 중요한 힌트입니다. 단순히 스택 크기를 늘리려고 하기보다 왜 함수가 끝나지 않고 계속 호출되는지를 찾는 것이 올바른 해결 방법입니다.