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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?