実務で「再帰が自然で書きやすい」代表例を具体的な説明+実用的なコード例(再帰版と必要なら非再帰版のワンポイント)でまとめます。JavaScript(ブラウザ / Node)でそのまま試せる形にしています。使いどころ・利点・注意点も書くので、すぐ応用できます。
1) DOMツリー操作(ブラウザ)
用途例:複雑な DOM を全検索して条件に合う要素へ処理、あるいは階層情報(深さ/パス)を計算する時。
再帰が自然な理由:DOM は本質的にツリー構造。各ノードに対して「同じ処理」を子ノードに適用するパターンがそのまま再帰になる。
例:指定したクラス名を持つ要素を深さ優先で全取得(再帰):
function findByClass(node, className, out = []) {
if (node.classList && node.classList.contains(className)) {
out.push(node);
}
for (let child of node.children) {
findByClass(child, className, out);
}
return out;
}
// 使い方(ページ全体)
const matches = findByClass(document.documentElement, "target-class");
console.log(matches);
JavaScript非再帰(スタックを使う)ワンポイント:
function findByClassIter(root, className) {
const stack = [root];
const out = [];
while (stack.length) {
const node = stack.pop();
if (node.classList && node.classList.contains(className)) out.push(node);
for (let i = node.children.length - 1; i >= 0; i--) stack.push(node.children[i]);
}
return out;
}
JavaScript→ 再帰よりスタック版はコールスタック問題を回避できるが、コードが少し冗長になる。
2) ネストした JSON の検索・集約(API レスポンスや設定ファイル)
用途例:JSON の深いネストから特定キー/値を探したり、特定フォーマットのフィールドだけ抽出する。
再帰が自然な理由:JSON は入れ子(配列/オブジェクト)の再帰的構造。鍵を辿っていく処理はそのまま自己呼び出しで表現できる。
例:オブジェクトのどこかにある特定のキー id を全て集める:
function collectIds(obj, out = []) {
if (obj && typeof obj === "object") {
if ("id" in obj) out.push(obj.id);
for (const k in obj) {
collectIds(obj[k], out);
}
}
return out;
}
// テスト
const data = { id: 1, items: [{id:2, children:[{id:3}]}] };
console.log(collectIds(data)); // [1,2,3]
JavaScript注意点:循環参照(self-referential object)があり得る場合は、WeakSet 等で訪問済みチェックを入れること。
3) ツリー表示/レンダリング(React 等の UI)
用途例:ネストしたメニューやコメントツリーをコンポーネントでレンダリングする。
再帰が自然な理由:各ノードは「子ノードを同じコンポーネントで描画する」ためコードが非常に簡潔になる。
React の例(再帰コンポーネント):
// NodeItem.jsx
export default function NodeItem({ node }) {
return (
<li>
{node.title}
{node.children && node.children.length > 0 && (
<ul>
{node.children.map(child => (
<NodeItem key={child.id} node={child} />
))}
</ul>
)}
</li>
);
}
JSX→ これだけで深さ無制限のツリーが描ける。非再帰で同じことをするのはJSXの生成ロジックが煩雑になる。
注意:非常に深いツリーだとブラウザのレンダリングやReactの再帰が問題になることがある(パフォーマンス見積もりを)。
4) ファイルシステムの再帰的探索(Node.js)
用途例:ディレクトリを再帰してファイルを列挙、特定拡張子を検索する CLI ツール。
再帰が自然な理由:ディレクトリは階層構造そのもの。
Node の同期例(理解用):
const fs = require('fs');
const path = require('path');
function walkDir(dir, cb) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walkDir(full, cb);
} else {
cb(full);
}
}
}
// 使い方
walkDir("./", filePath => console.log(filePath));
JavaScript非同期/ストリーミングや巨大ディレクトリでは再帰の深さとI/O量に注意。非再帰(キュー)で実装する選択肢もある。
5) 構文木(AST)操作・コード解析・変換
用途例:コンパイラやトランスパイラで AST を辿って最適化や変換を行う(例:Babel プラグイン、静的解析)。
再帰が自然な理由:AST は再帰的ノード構造。ノードごとに処理をし、子ノードに同じ処理を適用する。
簡単な例(疑似コード):
function visit(node) {
// ノードの種類に応じた処理
if (node.type === 'BinaryExpression') {
// 左右を再帰で訪問
visit(node.left);
visit(node.right);
} else if (node.type === 'FunctionDeclaration') {
for (const stmt of node.body) visit(stmt);
}
// ... 他ノード
}
JavaScript→ 再帰で書くと変換ルールが読みやすく保守的。
6) ネストしたバリデーション(フォーム/JSON スキーマ)
用途例:入れ子になったフォームデータや設定を再帰的に検証(必須チェック、型チェック、条件付き必須など)。
再帰が自然な理由:属性や子要素に同じバリデーションルールを適用するため。
例(シンプルな再帰バリデータ):
function validate(node, errors = []) {
if (node.required && (node.value === undefined || node.value === "")) {
errors.push(`${node.name} is required`);
}
if (node.children) {
for (const child of node.children) validate(child, errors);
}
return errors;
}
JavaScript7) 深いコピー(Deep clone)や差分計算
用途例:オブジェクト/配列を再帰的に複製、または差分(diff)を取る。
再帰が自然な理由:入れ子構造を個別に複製/比較していく必要があるため。
簡易 deep clone:
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== "object") return obj;
if (seen.has(obj)) return seen.get(obj); // 循環対応
const out = Array.isArray(obj) ? [] : {};
seen.set(obj, out);
for (const k in obj) out[k] = deepClone(obj[k], seen);
return out;
}
JavaScript注意:関数や特殊オブジェクト(Date, RegExp, Map, Set など)は別途ハンドリングが必要。
実務での「再帰を選ぶ基準」と注意点(まとめ)
- 選ぶ理由:データが自然にツリー/入れ子になっていて「同じ処理を子に適用する」パターンが明確なとき。コードが簡潔で読みやすくなる。
- 性能上の注意:深い入れ子(数千階層など)ではコールスタックが限界になりやすい → スタックを自前で使う反復実装に置き換える。
- 安全対策:循環参照があり得るデータなら訪問済みチェック(
Set/WeakSet)を入れる。 - デバッグ:
console.logで入出力を追うか、ブラウザのデバッガでコールスタックを観察すると流れが掴みやすい。 - UI(React等)での注意:再帰コンポーネントは便利だが、レンダリング性能とキーの付け方(
key)に気をつける。
練習課題(おすすめ)
- ブラウザで DOM ツリーを再帰で探索し、すべての
imgタグのsrcを配列にして返す。 - ネストした JSON から
type:"error"のオブジェクトをすべて抽出する。 - React コンポーネントで再帰的にネストされたコメントツリーを表示する(reply 機能付き)。
- Node.js でディレクトリを再帰して
.logファイルだけを列挙する CLI を作る(循環は無いが大量ファイルに注意)。

