RangeError: Maximum call stack size exceeded

関数の呼び出しが深くなりすぎたときに出ます。ほとんどは終わらない再帰です。止まる条件の書き忘れ、相互に呼び合う形、大きな配列の展開まで、原因別に直し方をまとめます。

実行して検証済み処理系Node 22.22.3難易度●●●○○目安9更新

関数の呼び出しが深くなりすぎたときに出ます。 ほとんどは、終わらない再帰です。

javascript
function f(n) {
return f(n + 1);
}

console.log(f(0));
RangeError: Maximum call stack size exceeded
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
RangeError true

関数を呼ぶと、戻り先を覚えておく場所(コールスタック)を1段使います。 戻らないまま呼び続けると、その置き場所が尽きます。

何段まで積めるのか

決まっていません。 処理系・環境・関数の中身で変わります。

javascript
function countdown(n) {
countdown(n - 1);
}

countdown(3);
RangeError: Maximum call stack size exceeded
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
3 2 1 0

何段だったかは書きません。 実行するたびに変わり、環境でも変わるからです。 「数千から数万段」程度とだけ思ってください。

深さに頼った設計をしないでください。 手元で通っても、別の環境で落ちます。

原因1: 止まる条件が無い

再帰には基底条件(これ以上呼ばないで返す条件)が要ります。

javascript
function countdown(n) {
if (n === 0) return;

countdown(n - 1);
}

countdown(3.5);
RangeError: Maximum call stack size exceeded
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
おわり おわり

n が減り続けるだけで、止まる場所がありません。 0 を通り過ぎ、負の数へ落ちていきます。

javascript
function walk(list) {
if (list.length === 0) return 0;

return 1 + walk(list);
}

console.log(walk([1, 2, 3]));
RangeError: Maximum call stack size exceeded
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
3

再帰を書いたら、まず止まる条件を書いてください。

原因2: 条件はあるが、そこへ辿り着かない

書いてあるのに効いていない形です。いちばん見つけにくい。

javascript
function isEven(n) {
return isOdd(n - 1);
}

function isOdd(n) {
return isEven(n - 1);
}

console.log(isEven(4));
RangeError: Maximum call stack size exceeded
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
true false

3.5 から1ずつ引いても、0 にはなりません。-0.5 -1.5 … と通り過ぎます。

=== ではなく範囲で書いてください。

javascript
const obj = {
get name() {
return this.name;
},
};

console.log(obj.name);
RangeError: Maximum call stack size exceeded
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
あかり

同じことは、値が減らないときにも起きます。

javascript
const big = new Array(200000).fill(1);

console.log(Math.max(...big));
RangeError: Maximum call stack size exceeded
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
99

walk(list)同じ配列を渡しています。減らないので終わりません。 walk(list.slice(1)) が正しい形です。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
5000050000
javascript
function sumTo(n) {
return n <= 0 ? 0 : n + sumTo(n - 1);
}

console.log(sumTo(100000));
RangeError: Maximum call stack size exceeded

原因3: 互いに呼び合っている

1つの関数を見ても再帰に見えないので、見落としやすい形です。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ 1, 3, 4, 2 ]
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
countdown(3) countdown(2) countdown(1) countdown(0)

isEvenisOddisEven … と往復し続けます。 どちらにも止まる条件がありません。

同じ形は、getter や toString の中で自分自身を読むときにも起きます。

this.name が同じ getter を呼びます。別の名前に持たせてください。

原因4: 大きな配列を引数に展開した

再帰でなくても出ます。...apply は、配列の要素を全部引数にします。 引数の数にも上限があります。

要素が20万個なので、引数を20万個渡したことになります。

展開せずに回してください。

上限も環境によって変わります。数万個までなら通ることが多いですが、当てにしないでください。

深い再帰を書き換える

再帰の形が自然でも、深くなるなら繰り返しに直します。

再帰のままだと、この規模で落ちます。

末尾呼び出し最適化には頼らないでください。 仕様にはありますが、V8(Node / Chrome)は実装していません

木構造をたどるように、深さが知れている場合だけ再帰を使います。 深さが入力に比例するなら、自分で置き場所(スタック)を持ちます。

pop() で取り出すので、後ろから先に見ます。順序を揃えたいなら shift() を使うか、push する順を逆にします。

似ているエラーとの違い

文言 何が起きたか
Maximum call stack size exceeded 呼び出しが深すぎる(この記事)
JavaScript heap out of memory 記憶が足りない(配列を無限に伸ばしたなど)
(何も出ずに固まる) 終わらない while / forスタックは使わないので落ちない

終わらないループはこのエラーになりません。 固まったまま帰ってきません。 落ちてくれるだけ、再帰のほうが気づきやすいとも言えます。

処理系によって文言が違う

処理系 文言
Node / Chrome(V8) Maximum call stack size exceeded
Firefox too much recursion
Safari Maximum call stack size exceeded

このページの実行結果は Node 22.22.3 のものです。

探しかたの順序

  1. その関数が自分を呼んでいないか見る
  2. 呼んでいるなら、止まる条件があるか=== なら範囲に直す
  3. 呼び出しのたびに値が確実に近づいているか。同じ値を渡していないか
  4. 自分を呼んでいないなら、互いに呼び合う相手を探す
  5. 再帰が無いなら、...apply大きな配列を渡していないか

深さを引数で持って出力すると、どこまで潜っているかが見えます。 値が減っていなければ、そこが原因です。

配列を1つの値にまとめる処理は、再帰で書かなくても reduce() で足りることがあります。 → Array.prototype.reduce()

入れ子の配列を平らにする処理も、自分で再帰を書かずに flat() で済みます。 → 入れ子の配列を平らにする

この記事の根拠

  1. Execution Contexts — ECMAScript® 2026 Language Specificationtc39.es
  2. RangeError — MDNdeveloper.mozilla.org
  3. Function.prototype.apply — ECMAScript® 2026 Language Specificationtc39.es

掲載しているコード例は、公開前に読み手が押したときに動くのと同じもので実行して出力を突き合わせています。結果はリポジトリに残しています。

このページの原文(Markdown)