Engineer Post||5 min

JavaScript filter() Practical Patterns for Business Systems

JavaScript filter() Practical Patterns for Business Systems

こんにちは!株式会社雲海設計の技術部です。

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のフィルタリング処理による複合条件適用と性能指標の可視化
JavaScriptのフィルタリング処理による複合条件適用と性能指標の可視化

なぜ今、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つの罠

  1. truthy判定の誤解: filter(u => u.age)はage=0の要素を落とす。意図しない除外が発生

  2. 非同期関数を渡せない: filter(async u => await check(u))はPromiseオブジェクトが返るため全要素がtruthy扱いになり、絞り込みが効かない

  3. 戻り値の使い忘れ: 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.43回走査ぶん
filter + includes (照合先1000件)1820O(n×m)で爆発
filter + Set.has (照合先1000件)3.8includes比 約480倍
filter + map 2段4.5許容範囲
reduceでワンパス3.2readabilityとトレードオフ
for-of + push2.6最速だが手続き的

注目すべきはfilter+includesの遅さです。「動くからいい」で見過ごされがちですが、対象データが増えた瞬間に画面が固まります。マスタ照合は問答無用でSetに詰めるを社内規約にしてもいいレベルです。


filter vs find vs some — 何を使うべき?

用途で明確に分かれます。混同するとパフォーマンスも可読性も損なわれます。

メソッド戻り値用途計算量
filter配列条件に合う全要素を取る必ずO(n)全走査
find要素 or undefined最初の1件を取る見つかれば即終了
someboolean1件でも存在するか判定見つかれば即終了
everyboolean全件が条件を満たすか外れれば即終了

「1件あればいい」のにfilterを書いてlength > 0 を見ているコードは典型的なアンチパターンです。レビューで必ずsomeに書き換えます。

// ❌ 全走査してから長さチェック
if (users.filter(u => u.isAdmin).length > 0) { ... }

// ✅ 1件見つかれば即true
if (users.some(u => u.isAdmin)) { ... }

業務コードで守りたい4つの設計原則

  1. 述語関数は名前を付ける: 3条件以上は必ず関数化。テスタブルになり、ドメイン語彙がコードに残る

  2. マスタ照合は事前にSet化: ループ外で1度だけnew Set()する。filter内でnew Setを書くのは論外

  3. チェーンは2段まで: filter→map→filter→mapは大規模配列で見直し対象。reduceかfor-ofに置換

  4. 非同期判定は 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にしましょう。