こんにちは!株式会社雲海設計の技術部です。
「javascript filterで複合条件を書いたら可読性が落ちた」「filterをチェーンしすぎて10万件の配列で画面がフリーズした」「filterとfindとreduceの使い分けがチーム内で揃わない」——2026年6月現在、業務システム開発の現場でjavascript filterに関する設計レビュー相談が、技術部に毎月のように寄せられています。本記事では、Array.prototype.filter()を業務コードに落とし込む際の実装パターンを、複合条件・パフォーマンス・null安全の3軸で実コード付きに整理します。
TL;DR
javascript filterは宣言的で読みやすい反面、O(n)走査の連結に注意。10万件超で2回以上チェーンするとUIブロッキングのリスク
複合条件は述語関数を名前付きで切り出すのが鉄則。インラインのアロー関数を3つ以上&&で繋ぐコードはレビューで弾く
「filter + includes」はO(n×m)になる罠。マスタ照合はSetに詰めてからhas()に書き換えるだけで100倍速くなるケースもある
大規模配列はfilter単発よりfor-of + push、もしくはreduceでワンパス化が速い。ただし可読性とのトレードオフで判断
null安全はfilter(Boolean)とtype predicate (x is T)でTypeScript型を絞り込むのが2026年の定石

なぜ今、javascript filterの実装パターンを論点化するのか?
結論から言うと、業務システムのフロントエンドが扱うデータ量が年々増え、安易なfilter連鎖がUXを壊すケースが2025年から顕著になったからです。SaaS化と内製化の波で、サーバーサイドで絞り込んでいたデータをブラウザに丸ごと渡し、クライアントでフィルタするUI設計が増えました。結果として、filterの書き方ひとつで体感速度が10倍変わる事態が珍しくありません。
State of JS 2025のレポートでも、配列メソッドの利用率トップはfilter/map/reduceの3点セットで、回答者の9割以上が日常的に使うと答えています。しかし「パフォーマンスを意識して書いている」と回答した開発者は4割に届かず、「書けるが最適化はしていない」層が過半という構造です。
filter()は宣言的で美しいが、N回走査されるという事実をチームで共有しないと、いずれ必ずパフォーマンス障害として顕在化する。— 社内コードレビュー指針より
javascript filterの基本動作と落とし穴は?
Array.prototype.filter()は「述語関数がtruthyを返した要素だけを集めた新しい配列を返す」シンプルなメソッドです。元配列は変更しません(非破壊)。基本形は次の通り。
const users = [
{ id: 1, name: 'Sato', active: true, age: 32 },
{ id: 2, name: 'Suzuki', active: false, age: 41 },
{ id: 3, name: 'Tanaka', active: true, age: 28 },
];
const activeUsers = users.filter(u => u.active);
// => [{id:1,...}, {id:3,...}]初学者が踏みやすい3つの罠
truthy判定の誤解:
filter(u => u.age)はage=0の要素を落とす。意図しない除外が発生非同期関数を渡せない:
filter(async u => await check(u))はPromiseオブジェクトが返るため全要素がtruthy扱いになり、絞り込みが効かない戻り値の使い忘れ: filterは非破壊。
users.filter(...)だけ書いて結果を変数に受けない初心者コードが今も時々ある
業務で頻出する実装パターン10選
現場のコードレビューで頻繁に登場するパターンを、業務シナリオに沿って整理します。
パターン1: 単純条件 — 状態フラグでの絞り込み
const visibleTasks = tasks.filter(t => !t.archived);パターン2: 複合AND条件 — 述語関数の名前付け切り出し
悪い例とリファクタ後を並べます。
// ❌ レビューで弾くべき例
const result = orders.filter(o =>
o.status === 'paid' && o.amount > 10000 && o.region === 'JP' && !o.refunded
);
// ✅ 述語を名前付き関数に切り出す
const isHighValuePaidJP = (o) =>
o.status === 'paid' &&
o.amount > 10000 &&
o.region === 'JP' &&
!o.refunded;
const result = orders.filter(isHighValuePaidJP);パターン3: OR条件と動的フィルタ
const allowedStatuses = ['paid', 'shipped', 'delivered'];
const statusSet = new Set(allowedStatuses);
const result = orders.filter(o => statusSet.has(o.status));パターン4: マスタ照合 — includesからSet.hasへ
これが現場で最も効くチューニングです。
// ❌ O(n × m): 10万件 × 1000件 で 1億回比較
const result = users.filter(u => targetIds.includes(u.id));
// ✅ O(n + m): Setに詰めてhas()で照合
const targetSet = new Set(targetIds);
const result = users.filter(u => targetSet.has(u.id));パターン5: null/undefined除去とTypeScript型絞り込み
// JavaScript
const clean = items.filter(Boolean);
// TypeScript: type predicateで型まで絞る
const clean = items.filter(
(x): x is User => x !== null && x !== undefined
);パターン6: 検索ボックスとの組み合わせ
const keyword = query.trim().toLowerCase();
const result = users.filter(u =>
u.name.toLowerCase().includes(keyword) ||
u.email.toLowerCase().includes(keyword)
);パターン7: 重複除去 — filter + indexOfの定番
// 小規模ならOK
const uniq = arr.filter((v, i, self) => self.indexOf(v) === i);
// 大規模はSetが圧倒的に速い
const uniq = [...new Set(arr)];パターン8: インデックス付きフィルタ
const oddIndexed = arr.filter((_, i) => i % 2 === 1);パターン9: 非同期判定が必要な場合
// ❌ これは動かない
const result = users.filter(async u => await isValid(u));
// ✅ Promise.allで判定結果配列を作ってから絞る
const flags = await Promise.all(users.map(u => isValid(u)));
const result = users.filter((_, i) => flags[i]);パターン10: filter + mapを reduce でワンパス化
// 2回走査
const result = orders.filter(o => o.paid).map(o => o.amount);
// 1回走査 (大規模配列で有効)
const result = orders.reduce((acc, o) => {
if (o.paid) acc.push(o.amount);
return acc;
}, []);パフォーマンス比較 — 10万件で何が起きるか?
結論から言うと、10万件規模ではfilter単独は十分速いが、includes併用とチェーン3段以上で破綻します。代表的な書き方を社内で計測した結果が次の表です(Node.js 22, M2 Mac, 配列長10万件, 平均5回)。
| パターン | 所要時間 (ms) | 備考 |
|---|---|---|
| filter単発(単純条件) | 2.1 | ベースライン |
| filter + filter + filter チェーン | 6.4 | 3回走査ぶん |
| filter + includes (照合先1000件) | 1820 | O(n×m)で爆発 |
| filter + Set.has (照合先1000件) | 3.8 | includes比 約480倍 |
| filter + map 2段 | 4.5 | 許容範囲 |
| reduceでワンパス | 3.2 | readabilityとトレードオフ |
| for-of + push | 2.6 | 最速だが手続き的 |
注目すべきはfilter+includesの遅さです。「動くからいい」で見過ごされがちですが、対象データが増えた瞬間に画面が固まります。マスタ照合は問答無用でSetに詰めるを社内規約にしてもいいレベルです。
filter vs find vs some — 何を使うべき?
用途で明確に分かれます。混同するとパフォーマンスも可読性も損なわれます。
| メソッド | 戻り値 | 用途 | 計算量 |
|---|---|---|---|
| filter | 配列 | 条件に合う全要素を取る | 必ずO(n)全走査 |
| find | 要素 or undefined | 最初の1件を取る | 見つかれば即終了 |
| some | boolean | 1件でも存在するか判定 | 見つかれば即終了 |
| every | boolean | 全件が条件を満たすか | 外れれば即終了 |
「1件あればいい」のにfilterを書いてlength > 0 を見ているコードは典型的なアンチパターンです。レビューで必ずsomeに書き換えます。
// ❌ 全走査してから長さチェック
if (users.filter(u => u.isAdmin).length > 0) { ... }
// ✅ 1件見つかれば即true
if (users.some(u => u.isAdmin)) { ... }業務コードで守りたい4つの設計原則
述語関数は名前を付ける: 3条件以上は必ず関数化。テスタブルになり、ドメイン語彙がコードに残る
マスタ照合は事前にSet化: ループ外で1度だけnew Set()する。filter内でnew Setを書くのは論外
チェーンは2段まで: filter→map→filter→mapは大規模配列で見直し対象。reduceかfor-ofに置換
非同期判定は Promise.all → filter: filterに直接asyncを渡さない
これらは雲海設計のリファクタリング指針でも標準ルールとして運用しています。TypeScript案件であれば 型を活かしたフィルタ設計とセットで考えるのがおすすめです。
雲海設計のフロントエンド品質支援
「filter一つの書き方が変わるだけで、画面のフリーズが解消した」「リファクタで保守コストが半分になった」——こうした地味な改善は、現場のレビュー文化とコーディング規約がなければ続きません。雲海設計では、Web開発・デザインとITコンサルティングの両面から、フロントエンドのコード品質改善と内製チームの底上げを支援しています。
「自社のJavaScriptコードを棚卸しして、パフォーマンスと可読性のホットスポットを洗い出したい」「内製チームのコードレビュー基準を整備したい」といったご相談は、お問い合わせからお気軽にどうぞ。
よくある質問
Q. filterとmapの順序はどちらが先が良いですか?
A. 原則filter→mapです。先に件数を減らしてからmapするほうが、中間配列が小さくなり高速かつメモリ効率も良くなります。逆順は重い変換を不要な要素にも適用してしまいます。
Q. 巨大配列でfilterが遅いときは何から手を付けますか?
A. ①includes/indexOf併用がないか確認(Set化で大半が解決)、②チェーン段数を数える(3段以上はreduceかfor-of化)、③そもそもサーバー側で絞れないか再検討、の順です。多くの場合①だけで体感が劇的に変わります。
Q. filter(Boolean)は本番コードで使ってよいですか?
A. 使って問題ありません。ただしage=0やcount=0のような意味のあるfalsy値を落とす可能性がある場面では、明示的にx !== null && x !== undefinedを書いた方が安全です。TypeScriptではtype predicate付きの自作ヘルパが定石です。
Q. filterの代わりにfor文を書くのはアリですか?
A. 性能が支配的な箇所ではアリです。ただし最初からfor文で書くのではなく、まずfilterで宣言的に書いて、計測して問題が出た箇所だけfor-ofや手続き型に書き換える、という順序を守ってください。早すぎる最適化は可読性を犠牲にするだけです。
Q. ReactのrenderでfilterをそのままJSXに書いて大丈夫ですか?
A. 配列が小さければ問題ありませんが、再レンダリングのたびに走るためuseMemoでラップするのが基本です。さらに、フィルタ後の配列に対するkey設計を間違えるとReconciliationコストが膨らみます。filter結果のidをkeyにしましょう。