Skip to Content
Grab N GoFiltering

Filtering

Syntax

Array.filter()
const nums = [1, 2, 3, 4, 5] nums.filter((n) => n > 3) // [4, 5] nums.filter((n) => n % 2 === 0) // [2, 4] // returns a new array — original unchanged // keeps items where callback returns true
Type Narrowing
const mixed = ['a', null, 'b', undefined, 'c'] // filter + type guard narrows the return type const strings = mixed.filter( (x): x is string => x !== null && x !== undefined ) // type is string[] — not (string | null | undefined)[]
Boolean Filter
const items = ['a', '', 'b', null, 'c', 0, false] // remove all falsy values items.filter(Boolean) // ['a', 'b', 'c'] // typed — use a real type guard, not Boolean const strings = items.filter( (x): x is string => typeof x === 'string' && x.length > 0 )
Unique Values
const nums = [1, 2, 2, 3, 3, 3] // Set for primitives const unique = [...new Set(nums)] // [1, 2, 3] // filter for index-based uniqueness nums.filter((n, i, arr) => arr.indexOf(n) === i)
Object.entries Filter
const obj = { a: 1, b: null, c: 3, d: undefined } // filter out null/undefined values const clean = Object.fromEntries( Object.entries(obj).filter(([, v]) => v != null) ) // { a: 1, c: 3 }
Map Filter (by value)
const scores = new Map([['Alice', 90], ['Bob', 60], ['Eve', 85]]) const passing = new Map( [...scores].filter(([, score]) => score >= 70) ) // Map { 'Alice' => 90, 'Eve' => 85 }

Beginner Patterns

Filter by Property
interface Task { text: string; done: boolean } const tasks: Task[] = [ { text: 'Buy milk', done: true }, { text: 'Write code', done: false }, { text: 'Ship it', done: false }, ] const active = tasks.filter((t) => !t.done) const completed = tasks.filter((t) => t.done)
Filter by Category
type Tab = 'all' | 'active' | 'completed' function filterTasks(tasks: Task[], tab: Tab) { if (tab === 'all') return tasks if (tab === 'active') return tasks.filter((t) => !t.done) return tasks.filter((t) => t.done) }
Exclude by ID
function removeItem<T extends { id: string }>(items: T[], id: string) { return items.filter((item) => item.id !== id) } const updated = removeItem(users, 'user-123') // all users except user-123
Filter + Map Chain
const users = [ { name: 'Alice', age: 17 }, { name: 'Bob', age: 25 }, { name: 'Eve', age: 30 }, ] // filter then transform const adultNames = users .filter((u) => u.age >= 18) .map((u) => u.name) // ['Bob', 'Eve']
Date Range Filter
function inRange(items: Dated[], start: Date, end: Date) { return items.filter((item) => { const d = new Date(item.date) return d >= start && d <= end }) } const thisWeek = inRange(events, weekStart, weekEnd)
Group By
function groupBy<T>(items: T[], key: keyof T): Map<T[keyof T], T[]> { const map = new Map<T[keyof T], T[]>() for (const item of items) { const val = item[key] if (!map.has(val)) map.set(val, []) map.get(val)!.push(item) } return map } const byCategory = groupBy(products, 'category') // Map { 'electronics' => [...], 'clothing' => [...] }

Intermediate Patterns

Multi-Filter Object
interface Filters { category?: string minPrice?: number maxPrice?: number inStock?: boolean } function applyFilters(products: Product[], filters: Filters) { return products.filter((p) => { if (filters.category && p.category !== filters.category) return false if (filters.minPrice != null && p.price < filters.minPrice) return false if (filters.maxPrice != null && p.price > filters.maxPrice) return false if (filters.inStock && !p.inStock) return false return true }) }
Filter Pipeline
type Predicate<T> = (item: T) => boolean function pipeline<T>(items: T[], ...predicates: Predicate<T>[]) { return items.filter((item) => predicates.every((fn) => fn(item))) } const results = pipeline( products, (p) => p.price < 100, (p) => p.category === 'electronics', (p) => p.rating >= 4, )
Unique by Key
function uniqueBy<T>(items: T[], key: keyof T): T[] { const seen = new Set() return items.filter((item) => { const val = item[key] if (seen.has(val)) return false seen.add(val) return true }) } uniqueBy(users, 'email') // one user per email
Partition
function partition<T>( items: T[], predicate: (item: T) => boolean ): [T[], T[]] { const pass: T[] = [] const fail: T[] = [] for (const item of items) { ;(predicate(item) ? pass : fail).push(item) } return [pass, fail] } const [adults, minors] = partition(users, (u) => u.age >= 18) // one pass instead of two .filter() calls
Search + Filter Combo
function searchAndFilter( products: Product[], query: string, category: string ) { return products .filter((p) => category === 'all' || p.category === category) .filter((p) => p.name.toLowerCase().includes(query.toLowerCase()) ) } searchAndFilter(products, 'phone', 'electronics')
Parse Filters from URL
function filtersFromURL(url: string): Record<string, string> { const { searchParams } = new URL(url) const filters: Record<string, string> = {} searchParams.forEach((value, key) => { filters[key] = value }) return filters } filtersFromURL('https://example.com/products?category=shoes&min=50') // { category: 'shoes', min: '50' }

Advanced Patterns

Composable Filter Builder
class FilterBuilder<T> { private predicates: ((item: T) => boolean)[] = [] where(fn: (item: T) => boolean) { this.predicates.push(fn) return this } apply(items: T[]) { return items.filter((item) => this.predicates.every((fn) => fn(item)) ) } } const results = new FilterBuilder<Product>() .where((p) => p.price < 50) .where((p) => p.inStock) .apply(products)
Intersection & Difference
function intersect<T>(a: T[], b: T[]): T[] { const set = new Set(b) return a.filter((item) => set.has(item)) } function difference<T>(a: T[], b: T[]): T[] { const set = new Set(b) return a.filter((item) => !set.has(item)) } intersect([1, 2, 3], [2, 3, 4]) // [2, 3] difference([1, 2, 3], [2, 3, 4]) // [1]
Faceted Filter Counts
function facetCounts<T>( items: T[], key: keyof T ): Map<T[keyof T], number> { const counts = new Map<T[keyof T], number>() for (const item of items) { const val = item[key] counts.set(val, (counts.get(val) ?? 0) + 1) } return counts } const categoryCounts = facetCounts(products, 'category') // Map { 'electronics' => 12, 'clothing' => 8, ... }
Paginate
function paginate<T>(items: T[], page: number, perPage: number) { const start = (page - 1) * perPage return { data: items.slice(start, start + perPage), total: items.length, pages: Math.ceil(items.length / perPage), } } const page1 = paginate(products, 1, 10) // items 0-9 const page2 = paginate(products, 2, 10) // items 10-19 // combine with filter: paginate(filtered, page, 10)
Lazy Filter (generator)
function* lazyFilter<T>( items: Iterable<T>, predicate: (item: T) => boolean ) { for (const item of items) { if (predicate(item)) yield item } } // only processes items as they're consumed const gen = lazyFilter(hugeArray, (x) => x > 100) const first10 = Array.from({ length: 10 }, () => gen.next().value) // stops after finding 10 matches — doesn't scan the whole array
Bloom Filter (probabilistic)
// approximate membership test — fast, memory-efficient class BloomFilter { private bits: Uint8Array constructor(private size = 1024) { this.bits = new Uint8Array(size) } private hash(str: string, seed: number) { let h = seed for (const ch of str) h = (h * 31 + ch.charCodeAt(0)) % this.size return h } add(item: string) { this.bits[this.hash(item, 7)] = 1 this.bits[this.hash(item, 13)] = 1 } mightContain(item: string) { return this.bits[this.hash(item, 7)] === 1 && this.bits[this.hash(item, 13)] === 1 } } // false positives possible, false negatives never
Last updated on