---
type: errors
language: javascript
slug: maximum-call-stack
title: "RangeError: Maximum call stack size exceeded"
title_tag: "Maximum call stack size exceeded の原因と直し方"
summary: >
  関数の呼び出しが深くなりすぎたときに出ます。ほとんどは終わらない再帰です。
  止まる条件の書き忘れ、相互に呼び合う形、大きな配列の展開まで、原因別に直し方をまとめます。
description: >
  再帰が止まらないときに出ます。基底条件の書き忘れ、相互再帰、深すぎるデータ。スタックの読み方と、ループや reduce へ書き換える具体的な直し方を示します。
status: published
difficulty: 3
minutes: 9

versions:
  verified: "Node 22.22.3"
  since: null
  deprecated: null
  removed: null

sources:
  - title: "Execution Contexts — ECMAScript® 2026 Language Specification"
    url: "https://tc39.es/ecma262/#sec-execution-contexts"
  - title: "RangeError — MDN"
    url: "https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RangeError"
  - title: "Function.prototype.apply — ECMAScript® 2026 Language Specification"
    url: "https://tc39.es/ecma262/#sec-function.prototype.apply"

terms: [再帰, コールスタック, 基底条件]

links:
  related:
    - javascript/errors/is-not-a-function
    - javascript/reference/array/reduce

content_updated_at: 2026-09-07
published_at: 2026-09-07
---

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

```js bad
function f(n) {
  return f(n + 1);
}

console.log(f(0));
```
```output
RangeError: Maximum call stack size exceeded
```

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

## 何段まで積めるのか

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

```js run
let depth = 0;

function probe() {
  depth++;
  probe();
}

try {
  probe();
} catch (e) {
  console.log(e.constructor.name);
  console.log(depth > 1000);
}
```
```output
RangeError
true
```

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

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

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

再帰には[type:基底条件]（これ以上呼ばないで返す条件）が要ります。

```js bad
function countdown(n) {
  countdown(n - 1);
}

countdown(3);
```
```output
RangeError: Maximum call stack size exceeded
```

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

```js run
function countdown(n) {
  if (n < 0) return;

  console.log(n);
  countdown(n - 1);
}

countdown(3);
```
```output
3
2
1
0
```

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

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

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

```js bad
function countdown(n) {
  if (n === 0) return;

  countdown(n - 1);
}

countdown(3.5);
```
```output
RangeError: Maximum call stack size exceeded
```

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

**`===` ではなく[key:範囲]で書いてください。**

```js run
function countdown(n) {
  if (n <= 0) return 'おわり';

  return countdown(n - 1);
}

console.log(countdown(3.5));
console.log(countdown(3));
```
```output
おわり
おわり
```

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

```js bad
function walk(list) {
  if (list.length === 0) return 0;

  return 1 + walk(list);
}

console.log(walk([1, 2, 3]));
```
```output
RangeError: Maximum call stack size exceeded
```

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

```js run
function walk(list) {
  if (list.length === 0) return 0;

  return 1 + walk(list.slice(1));
}

console.log(walk([1, 2, 3]));
```
```output
3
```

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

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

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

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

console.log(isEven(4));
```
```output
RangeError: Maximum call stack size exceeded
```

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

```js run
function isEven(n) {
  if (n === 0) return true;
  return isOdd(n - 1);
}

function isOdd(n) {
  if (n === 0) return false;
  return isEven(n - 1);
}

console.log(isEven(4), isOdd(4));
```
```output
true false
```

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

```js bad
const obj = {
  get name() {
    return this.name;
  },
};

console.log(obj.name);
```
```output
RangeError: Maximum call stack size exceeded
```

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

```js run
const obj = {
  _name: 'あかり',
  get name() {
    return this._name;
  },
};

console.log(obj.name);
```
```output
あかり
```

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

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

```js bad
const big = new Array(200000).fill(1);

console.log(Math.max(...big));
```
```output
RangeError: Maximum call stack size exceeded
```

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

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

```js run
const big = new Array(200000).fill(1);
big[12345] = 99;

console.log(big.reduce((max, n) => (n > max ? n : max), -Infinity));
```
```output
99
```

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

## 深い再帰を書き換える

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

```js run
function sumTo(n) {
  let total = 0;

  for (let i = 1; i <= n; i++) {
    total += i;
  }

  return total;
}

console.log(sumTo(100000));
```
```output
5000050000
```

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

```js bad
function sumTo(n) {
  return n <= 0 ? 0 : n + sumTo(n - 1);
}

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

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

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

```js run
function walk(tree) {
  const stack = [tree];
  const out = [];

  while (stack.length > 0) {
    const node = stack.pop();
    out.push(node.v);

    for (const c of node.c ?? []) stack.push(c);
  }

  return out;
}

console.log(walk({ v: 1, c: [{ v: 2 }, { v: 3, c: [{ v: 4 }] }] }));
```
```output
[ 1, 3, 4, 2 ]
```

[dim: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` |

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

## 探しかたの順序

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

```js run
function countdown(n, depth = 0) {
  console.log('  '.repeat(depth) + `countdown(${n})`);

  if (n <= 0) return;
  countdown(n - 1, depth + 1);
}

countdown(3);
```
```output
countdown(3)
  countdown(2)
    countdown(1)
      countdown(0)
```

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

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

入れ子の配列を平らにする処理も、自分で再帰を書かずに `flat()` で済みます。
→ [入れ子の配列を平らにする](/ja/javascript/how-to/array/flatten/)
