- JavaScript
Maximum call stack size exceededの原因と直し方
RangeError: Maximum call stack size exceeded は、関数の呼び出しが深くなりすぎて、呼び出しを記録する領域(コールスタック)があふれたときに出るエラーです。 確認環境では、Node.js でも Chrome でも、約1万2千段の呼び出しで止まりました。
原因は、次の2種類に分けられます。
| 種類 | 例 | 直し方 |
|---|---|---|
| 終わらない呼び出し(バグ) | 再帰の終了条件の書き忘れ、setter の中で自分を呼ぶ | 終了条件・呼び出す先を直す |
| 正しいが深すぎる・多すぎる処理 | 深い木構造の再帰、Math.max(...大きな配列)、深い入れ子の JSON.stringify |
ループに書き換える、スプレッドを使わない |
確認環境:Node.js 22.22.2、Chromium 141、React 19.3.0(確認日 2026年10月10日)。各原因を実際に起こし、直したコードで動くことを確かめました。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Maximum call stack size exceededの原因と直し方」
エラーの場所を見つける
スタックトレースに同じ関数が何行も続いているところが、繰り返し呼ばれている場所です。
/tmp/cd.js:2
return countdown(n - 1);
^
RangeError: Maximum call stack size exceeded
at countdown (/tmp/cd.js:2:3)
at countdown (/tmp/cd.js:2:10)
at countdown (/tmp/cd.js:2:10)
at countdown (/tmp/cd.js:2:10)
ブラウザでは、開発者ツールの Console に出たエラーの左の三角を開くと、同じ形で表示されます。2つの関数が交互に並んでいれば、お互いを呼び合っている(A が B を呼び、B が A を呼ぶ)状態です。
| ブラウザ | 表示 |
|---|---|
| Chrome・Edge・Node.js | RangeError: Maximum call stack size exceeded |
| Firefox | InternalError: too much recursion |
| Safari | RangeError: Maximum call stack size exceeded. |
原因1:再帰の終了条件が無い
function countdown(n) { return countdown(n - 1); } // n === 0 で止める条件が無い
countdown(10);
// RangeError: Maximum call stack size exceeded
再帰関数には、「ここで止める」条件(終了条件)が要ります。条件はあっても、引数が条件に近づいていない(n - 1 のつもりが n のまま、など)と同じエラーになります。
function countdown(n) {
if (n <= 0) return 'done'; // 終了条件
return countdown(n - 1); // 条件に近づく引数で呼ぶ
}
原因2:setter・getter が自分自身を呼んでいる
class User {
set name(v) { this.name = v; } // this.name への代入が、この setter をまた呼ぶ
}
new User().name = 'A';
// RangeError: Maximum call stack size exceeded
this.name = v は、自分自身(set name)を呼び出します。値は別の名前に保存します。
class User {
#name = '';
get name() { return this.#name; }
set name(v) { this.#name = v.trim(); } // # のプライベートフィールドに保存
}
const u = new User();
u.name = ' 山田 ';
console.log(u.name); // "山田"
toString() の中で文字列結合('' + this)を使う、なども同じ形の原因です(文字列にするために toString() がまた呼ばれる)。
原因3:Math.max(...配列) や push(...配列) に大きな配列を渡した
スプレッド構文(...)で配列を引数に展開すると、要素の数だけ引数が並びます。引数もスタックに積まれるため、大きな配列では同じエラーになります。
Math.max(...100000件) → 99999
Math.max(...150000件) → RangeError: Maximum call stack size exceeded
[].push(...200000件) → RangeError: Maximum call stack size exceeded
Node.js では 125,000 件までは動き、150,000 件で止まりました。Chrome でも 120,000 件は動き、150,000 件で止まりました。データの件数が増えたときに、ある日突然エラーになるのが、この原因のやっかいなところです。
const big = Array.from({ length: 1_000_000 }, (_, i) => i);
// 最大値:reduce で1件ずつ比べる
const maxOf = (arr) => arr.reduce((m, v) => (v > m ? v : m), -Infinity);
console.log(maxOf(big)); // 999999
// 連結:ループで push するか concat
const dst = [];
for (const v of big) dst.push(v);
const joined = [].concat(big);
100万件でも動きました。件数が決まっていない配列には、スプレッドで引数を渡さないと決めておくと安全です。
原因4:深い木構造を再帰でたどった
部署の階層、コメントの返信のつながり、ファイルのフォルダなど、木構造を再帰でたどる処理は、深さが約1万を超えると止まります。深さ 100,000 の木で比べました。
// 再帰で数える(深いと止まる)
function countRecursive(node) {
let n = 1;
for (const c of node.children) n += countRecursive(c);
return n;
}
// 自分でスタックを持って数える(深さに関係なく動く)
function countIterative(root) {
let n = 0;
const stack = [root];
while (stack.length > 0) {
const node = stack.pop();
n++;
for (const c of node.children) stack.push(c);
}
return n;
}
再帰 : RangeError: Maximum call stack size exceeded
ループ: 100000
呼び出しの代わりに、配列(stack)に「次にたどるもの」を積むと、深さに関係なく動きます。積んだものを先頭から取り出す(shift)と、浅い順にたどる幅優先になります。
原因5:深い入れ子・循環参照の JSON.stringify
JSON.stringify は、内部で入れ子を再帰的にたどります。
深さ 20,000 の入れ子 → RangeError: Maximum call stack size exceeded
自分自身を参照する object → TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
--- property 'self' closes the circle
循環参照(オブジェクトが自分自身や親を参照している)は、別のエラー(TypeError)になります。メッセージに、循環しているプロパティ(self)が表示されます。
循環している部分だけを置き換えて JSON にするには、祖先(今たどっている経路)だけを覚えて比べます。
// 循環参照だけを "[循環]" に置き換える(MDN の例と同じ考え方:祖先だけを覚える)
function circularReplacer() {
const ancestors = [];
return function (key, value) {
if (typeof value !== 'object' || value === null) return value;
while (ancestors.length > 0 && ancestors.at(-1) !== this) ancestors.pop();
if (ancestors.includes(value)) return '[循環]';
ancestors.push(value);
return value;
};
}
JSON.stringify(a, circularReplacer()); // {"id":1,"self":"[循環]"}
よく見かける「一度見たオブジェクトを WeakSet に覚えて置き換える」書き方は、同じオブジェクトを2か所から参照しているだけ(循環ではない)でも置き換えてしまいます。
const o = { x: 1 };
祖先を覚える方法 : JSON.stringify([o, o]) → [{"x":1},{"x":1}]
WeakSet の方法 : JSON.stringify([o, o]) → [{"x":1},"[循環]"] ← 2つ目が消える
原因6:React・Vue の描画が終わらない
React で、描画の中で state を更新すると、描画→更新→描画…が続きます。React 19 では、スタックがあふれる前に React が止めて、別のエラーになりました。
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
function Counter() {
const [n, setN] = useState(0);
setN(n + 1); // 描画のたびに state を更新している
return <p>{n}</p>;
}
state の更新は、イベントの処理(onClick など)か useEffect の中で行います。useEffect の依存配列に、その中で更新する値を入れると、同じように更新が続きます。Vue では、watch の中で監視している値を書き換えると、同じ形になります。
スタックの大きさを増やす前に
Node.js には、スタックの大きさを変えるオプション(--stack-size)があり、確認環境では --stack-size=2000 で約2万5千段まで呼べました。ただし、ブラウザでは変えられず、大きくしすぎると Node.js 自体が異常終了することがあります。深さが決まっていないデータを扱うなら、ループへの書き換え(原因4)が確実です。
ほかの言語の同じエラー
| 言語 | エラー |
|---|---|
| Java | java.lang.StackOverflowError |
| Python | RecursionError: maximum recursion depth exceeded |
| C# | Stack overflow.(プロセスが終了する) |
どの言語でも、原因(終わらない呼び出し・深すぎる再帰)と直し方(終了条件、ループへの書き換え)は同じです。
確認の手順(チェックリスト)
| 順番 | 確認すること | 見るもの |
|---|---|---|
| 1 | 繰り返し呼ばれている関数 | スタックトレースで同じ関数が続く行 |
| 2 | 再帰なら終了条件 | 条件があるか、引数が条件に近づいているか |
| 3 | setter・getter・toString |
中で自分自身を呼んでいないか |
| 4 | ...配列 の引数 |
Math.max・push・apply に件数の決まっていない配列を渡していないか |
| 5 | データの深さ | 木構造の深さ、循環参照 |
| 6 | 画面の描画 | 描画中の state 更新、watch・useEffect での書き換え |
よくある質問
Q. 開発環境では動くのに、本番データでだけエラーになります。
A. データの件数や深さに比例して呼び出しが増える処理(原因3・4)の可能性が高いです。...配列 の引数と、再帰でたどる処理を探します。
Q. try...catch でエラーを捕まえて続ければよいですか。 A. 捕まえることはできますが、処理は途中で止まっています。データが欠けたまま進むことになるため、原因を直すほうが確実です。
Q. 再帰をやめると、コードが読みにくくなりませんか。 A. 深さが小さいと分かっている処理(画面のメニュー階層など)は、再帰のままで問題ありません。深さが利用者のデータで決まる処理だけを、ループにするのが現実的です。
まとめ
Maximum call stack size exceededは、関数の呼び出しが深くなりすぎたエラー。確認環境では約1万2千段で止まった- スタックトレースで同じ関数が続く場所を探す。交互に並べば呼び合い
- バグ型:終了条件の書き忘れ、setter の自己呼び出し、描画中の state 更新
- 件数・深さ型:
Math.max(...配列)は12万〜15万件の間で止まった。reduceやループにする。深い木は自分でスタックを持つ - 循環参照は別のエラー(TypeError)。置き換えるなら祖先だけを覚える方法で
参考・出典
確認日はいずれも 2026年10月10日です。
- MDN Web Docs「InternalError: too much recursion」:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Too_much_recursion
- MDN Web Docs「TypeError: cyclic object value」:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Maximum call stack size exceededの原因と直し方」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。