再帰とは、関数が自分を呼んで問題を小さくする仕組み

回 2-5 道 2 データ構造とアルゴリズム

再帰は、関数が自分自身を呼び、問題を 1 段小さくしてから同じ手順で解く書き方です。呼ぶたびに途中の状態が呼び出しのスタックに積まれ、止まる条件 (基底条件) に着くと、上に積まれた物から順に値が決まって戻ってきます。止まる条件が抜けると積み上がり続け、Python では上限の深さで RecursionError が出ます。

再帰の呼び出しを「積む行き」と「値が決まる帰り」に分けて、紙の上で 1 段ずつ追えるようになります。

自分を呼ぶ関数は、なぜ止まって答えを返せるのか

再帰の関数が止まれるのは、呼ぶたびに問題が 1 段小さくなり、いつか止まる条件に着くからです。答えは、そこから呼んだ順の逆に戻りながら組み立てられます。

2026年10月26日に Google で「再帰」を検索すると、結果の上に「もしかして 再帰」と、同じ言葉を勧める案内が出ます。押しても同じ検索に戻るだけです。止まる条件の無い再帰を見せる遊びです。

フォルダの中にフォルダがあり、リストの中にリストがある。こうした入れ子の物は中身が同じ形なので、同じ手順を中身に当てはめれば全部を片付けられます。この「自分と同じ手順を、小さくした問題に使う」書き方が再帰 (さいき) です。

この回では、階乗の呼び出しが積まれて戻る様子を 1 段ずつ出し、紙に書いて追えるようにします。

人形の中に小さい同じ人形があり、いちばん小さい所で止まり、戻りながら答えが決まる図

再帰はどう動くのか

再帰の関数は、止まる条件と、問題を 1 段小さくして自分を呼ぶ一歩の 2 つでできています。呼ぶたびに途中の状態が積まれ、止まる条件に着いてから、上の物から順に値が決まります。

2 つの決まり、止まる条件と小さくする一歩

  1. 止まる条件 (基底条件)。これ以上小さくせず、すぐ答えを返す場合。階乗なら「1 の階乗は 1」
  2. 小さくする一歩。問題を少し小さくして自分を呼び、その答えから今の答えを作る。階乗なら「n の階乗は、n に n − 1 の階乗を掛けた物」

英語では再帰を recursion、再帰の形のことを recursive と言い、自分の中に同じ形が出てくる性質を再帰性と呼びます。

一歩ごとに、止まる条件へ必ず近づくことが大切です。階乗では n が 1 ずつ減るので、正の整数から始めれば 1 に着きます。

階乗の再帰

def fact(n):
    if n == 1:  # 止まる条件
        return 1
    return n * fact(n - 1)

呼ぶたびに、途中の状態が積まれる

関数を呼ぶと、引数、途中の計算、戻る場所をまとめた記録 (フレーム) が、呼び出しのスタックに積まれます。スタックは前の回で見た、後から積んだ物が先に出るしまい方です。

fact(4) の 4 * fact(3) は、fact(3) の答えが出るまで終われません。そこで fact(4) は待ったまま残り、その上に fact(3) が積まれます。

段積まれた呼び出し待っている計算
4 (一番上)fact(1)なし。1 を返す
3fact(2)2 × fact(1)
2fact(3)3 × fact(2)
1 (一番下)fact(4)4 × fact(3)

止まる条件に着いてから、上から順に値が決まる

止まる条件に着くと、一番上の fact(1) が 1 を返して取り除かれ、その下の fact(2) が 2 × 1 を計算して 2 を返します。これを一番下まで繰り返し、最後に fact(4) が 24 を返します。

再帰は、行きで積み、帰りで値を決めて降ろす 2 段の動きです。行きの間は、どの段もまだ答えを知りません。図で段を 1 つずつ進めてください。

再帰の呼び出しスタック

「停止条件を削除する」を押すと箱が積み上がり続けます。本物の Python が止める深さは、崩れる前提の節で確かめます。

動かして確かめる

1 本目は、階乗の呼び出しを、呼ぶときと戻るときに 1 行ずつ出します。深さに合わせて字下げするので、出力の形がスタックの積み上がりになります。calls は、最初の fact(4) も含めて、呼ばれるたびに 1 を足します。出力では fact を f と略します。

動かす 階乗の呼ぶと戻るを字下げで出す

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

呼ぶ f(4)
  呼ぶ f(3)
    呼ぶ f(2)
      呼ぶ f(1)
      戻る 1
    戻る 2
  戻る 6
戻る 24
答え 24
呼んだ回数 4

上の 4 行が行き、次の 4 行が帰りです。最後に呼んだ f(1) が最初に 1 を返し、最初に呼んだ f(4) が最後に 24 を返します。最初の呼び出しも数えて 4 回呼ばれ、一番深いときに 4 段が積まれていました。

紙で追うときもこの形で書きます。呼ぶたびに 1 段右へずらして「呼ぶ」を書き、止まる条件に着いたら、同じ字下げに「戻る」と値を書きながら左へ戻ります。待っている掛け算を横に書くと、帰りで何を計算するかを見失いません。

2 本目は、仕事でよく出る 3 つの形、リストの合計、入れ子のリストを平らにする、フォルダの木の大きさを足す、を再帰で書きます。

動かす 合計、入れ子を平らに、木の大きさ

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

10
[1, 2, 3, 4, 5]
70
65

total は、リストを先頭の 1 つと残りに切り、残りの合計を自分に頼みます。止まる条件は空のリストです。

flat は、中身がリストでなければそれ 1 つのリストを返し、リストなら中の 1 つずつに自分を使ってつなぎます。何重の入れ子でも [1, 2, 3, 4, 5] になります。面接でよく出る「入れ子のリストを平らにする」問いはこの形です。

size は、フォルダを辞書、ファイルを大きさの数で表した木をたどります。全体は 3 + 40 + 25 + 2 で 70、img だけなら 65 です。深さの決まっていない木は、再帰で書くのが素直です。

total には弱点があります。xs[1:] は残りを新しいリストに写すので、長さ 5 なら 4 + 3 + 2 + 1 で 10 個を写します。図で長さを変えて確かめてください。位置 (index) を渡す形にすると写しは無くなります。

配列を先頭と残りに切る

再帰はどこで崩れるのか

再帰は、止まる条件が抜けたときと、入れ子が深すぎるときに崩れます。どちらもスタックが上限に届き、Python では RecursionError という例外で止まります。

止まる条件が抜けると、上限まで積み上がる

止まる条件の無い再帰は、積むだけで一度も降ろせません。Python は呼び出しの深さに上限を持ち、超えると RecursionError を出します。公式文書は、この上限を、無限の再帰で C のスタックがあふれて Python ごと落ちるのを防ぐ物と説明しています。

上限は sys.getrecursionlimit() で読み、sys.setrecursionlimit() で変えます。手元の CPython 3.14 では、何も変えずに読むと 1000 でした。ブラウザの Python では値が違うことがあります。

次のコードは、止まる条件の無い関数を上限 200 と 400 で動かし、潜れた深さを比べます。

動かす 止まる条件が無いと上限で止まる

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

200 RecursionError
400 RecursionError
深さの差 200
RuntimeError の仲間 True

どちらも RecursionError で止まり、上限を 200 増やすと、潜れた深さもちょうど 200 増えました。止まる深さを決めているのは上限の値で、上限を上げても、止まる条件が無ければ遅く止まるだけです。

RecursionError は RuntimeError の子のクラスで、公式文書によると Python 3.5 より前はただの RuntimeError でした。公式文書は、上げられる上限は環境で違い、高すぎると Python が落ちることがあるとも書いています。

入れ子が深すぎる入力は、ループに書き換える

止まる条件が正しくても、入力の入れ子が上限より深ければ再帰は止まります。外から来るデータの深さは、こちらで決められません。

再帰は、呼び出しのスタックの代わりに自前のスタック (リスト) を持てば、ループに書き換えられます。次のコードは、7 を 2 万重のリストで包んだ入力を、再帰の flat とループの flat_loop で平らにします。

動かす 深い入れ子を再帰とループで平らに

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

再帰 RecursionError
ループ [7]
[1, 2, 3, 4, 5]

再帰の flat は途中で上限に届き、flat_loop は最後まで処理しました。まだ見ていない物を todo に積んで 1 つずつ取り出すので、積む場所が上限のあるスタックからふつうのリストに変わっただけです。中身を逆順に積むのは、後から積んだ物が先に出るからで、こうすると左から順に出ます。

階乗のように自分を 1 回だけ呼ぶ形なら、スタックも要りません。

階乗をループで書く

def fact_loop(n):
    r = 1
    for k in range(2, n + 1):
        r *= k
    return r

自分を 2 回以上呼ぶ再帰は、同じ小さな問題を何度も解き直すことがあります。フィボナッチ数の素直な再帰がその例で、呼ぶ回数が急に増えます。この遅さと直し方は 2-9 の動的計画法の回で扱います。

再帰は仕事でどう確かめるのか

仕事では、止まる条件、そこへ近づく一歩、入力の深さの 3 つを確かめます。

  1. 関数の頭に、自分を呼ばずに返す if があるか。空のリスト、0、1、ファイルなど、一番小さい入力で何を返すか
  2. 引数が減る、リストが短くなる、木の下へ進むなど、一歩ごとに近づくか。同じ所へ戻る輪が無いか
  3. 外から来る JSON、XML、パス、入れ子のリストは、深さに上限を決めて断るか、自前のスタックのループで処理する
  4. 試験に、止まる条件そのもの、1 段の入力、深い入力の 3 つを入れる

試験の例

def test_flat():
    assert flat([]) == []
    assert flat([1]) == [1]
    box = 7
    for i in range(20000):
        box = [box]
    assert flat_loop(box) == [7]

関わる実例

Go の XML の読み取りが、深い入れ子でスタックを使い切った (2022年7月12日に修正)

Go の標準ライブラリの encoding/xml では、とても深く入れ子にした XML を読むと、Decoder.Skip がスタックを使い切って panic (異常終了) しました。CVE-2022-28131 です。修正は Skip を再帰から繰り返し (ループ) に書き直すもので、2022年7月12日の Go 1.18.4 と 1.17.12 に入りました。

崩れた前提は「入れ子は、再帰で潜っても足りる深さで終わる」です。深さは送る側が決められるので、細工した入力 1 つで止められました。上の 4 本目と同じ、ループへの書き換えで直しています。

出典は GO-2022-0521 (Go の脆弱性の記録) と 修正の変更 です。

Go の Glob が、区切りの多いパスでスタックを使い切った (2022年7月12日に修正)

同じ日には、path/filepath の Glob (パスの型に合うファイルを探す関数) に区切り記号のとても多いパスを渡すと、スタックを使い切って panic する脆弱性も直されました。CVE-2022-30632 です。こちらは、渡せる区切りの数に上限を付けて直しました。

崩れた前提は「パスの区切りは、ふつうのファイルの深さくらいで収まる」です。外から入力が届く再帰には、深さの上限か、ループへの書き換えのどちらかが要ります。

出典は GO-2022-0522 (Go の脆弱性の記録)、修正の変更、Go の版の履歴 です。

よくある問い

再帰とスタックオーバーフローは同じですか

違います。再帰は書き方の名前で、スタックオーバーフローは呼び出しを積む場所が尽きる失敗の名前です。止まる条件の無い再帰や深すぎる再帰が典型の原因で、Python は本物のスタックがあふれる前に RecursionError で止めます。

再帰はなぜ遅いと言われるのですか

呼ぶたびにフレームを積んで降ろす手間がかかり、書き方によっては同じ計算を何度もするからです。フィボナッチ数の素直な再帰がその例で、2-9 で答えを覚えて減らす方法を扱います。

再帰とループはどちらを使えばよいですか

木やフォルダ、入れ子のリストのように深さの決まっていない形は、再帰のほうが短く読みやすく書けます。合計や階乗のように 1 列に進むだけならループが素直です。深さの分からない外からの入力には、自前のスタックのループが安全です。

末尾再帰とは何ですか、Python では速くなりますか

末尾再帰は、自分を呼ぶのが最後の仕事で、戻った値をそのまま返す形です。言語によってはこれをループに直しますが、Python は直しません。Python の作者は2009年4月22日の記事で、直すと traceback (エラーの経路の表示) から段が消えて直しにくくなることなどを理由に、入れないと書いています。

確認クイズ

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。4 問すべてに正しく答えると、この回は修了で、石が緑に変わります (この端末のブラウザに残ります)。間違えた問いは、あとから解き直せます。

1 問目

このコードを動かすと何が出ますか。

呼ぶ前と後で出す

def g(n):
    if n == 0:
        return
    print("前", n)
    g(n - 1)
    print("後", n)


g(2)

2 問目

fact(4) が止まる条件 fact(1) に着いた瞬間、スタックにはいくつの fact が積まれていますか。最初の fact(4) も数えます。

3 問目

止まる条件を書き忘れた再帰の関数を Python で動かすと、何が起きますか。

4 問目

外から届く、深さの分からない入れ子の JSON を、再帰でたどっています。深い入力で止まらないようにする直し方はどれですか。

説明課題

fact(3) を再帰で計算するときの動きを紙に書いてください。呼ぶたびに 1 段右へずらして「呼ぶ fact(n)」を書き、止まる条件に着いたら、同じ字下げに「戻る 値」を書きながら左へ戻ります。待っている掛け算も添えます。書いたら、下の模範解答を開いて比べてください。

模範解答

自分の説明を書いてから開いて、比べてください。

この回の先の一歩

入れ子のデータやフォルダの木をたどる処理は、Web でもデータ処理でも書く場面があり、深さの上限を知っていると落ちる前に気づけます。いまの自分が転職までの道のどの段にいるかは、転職診断で確かめられます。

もっと深く

この回の続きを、ゆめさくのコースの章や道具で深められます。頁の中の確認クイズに答えたあと、手を動かす量を増やしたいときに使ってください。

一次資料

どれも 2026年10月26日に開いて、題と中身を確かめました。

次へ

道 2 データ構造とアルゴリズムの回

  1. 2-0アルゴリズムとは順次、分岐、反復の 3 つで書く手順頁で読む
  2. 2-1線形探索と二分探索100 万件でも 20 回で見つかる頁で読む
  3. 2-2バブルソートと挿入・選択ソート遅いソート 3 つ、2 倍で約 4 倍頁で読む
  4. 2-3クイックソートとマージソート速いソート 2 つ、平均と最悪頁で読む
  5. 2-4スタックとキュー積む順と並ぶ順頁で読む
  6. 2-5再帰自分を呼んで小さくするこの回
  7. 2-6木と二分探索木たどる順、片寄り頁で読む
  8. 2-7ハッシュテーブル鍵から一気に引く、衝突頁で読む
  9. 2-8ダイクストラ法と最短経路点と線、幅優先、重みのある道頁で読む
  10. 2-9動的計画法同じ計算を 2 度しない頁で読む
  11. 2-10試験と面接で確かめる基本情報 科目B、道場 10 問、面接 20 問頁で読む

道 2 の頁へ戻るほかの道を見る