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 trueType 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-123Filter + 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 emailPartition
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() callsSearch + 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 arrayBloom 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 neverLast updated on