Skip to Content
Grab N GoSearch

Search

Syntax

includes
const items = ['apple', 'banana', 'cherry'] items.includes('banana') // true items.includes('grape') // false 'hello world'.includes('world') // true
indexOf
const items = ['a', 'b', 'c', 'b'] items.indexOf('b') // 1 — first match items.indexOf('z') // -1 — not found items.lastIndexOf('b') // 3 — last match
find & findIndex
const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, ] users.find((u) => u.id === 2) // { id: 2, name: 'Bob' } users.findIndex((u) => u.id === 2) // 1 users.find((u) => u.id === 9) // undefined
some & every
const nums = [1, 2, 3, 4, 5] nums.some((n) => n > 4) // true — at least one nums.every((n) => n > 0) // true — all pass nums.every((n) => n > 3) // false
String Search
const str = 'The quick brown fox' str.startsWith('The') // true str.endsWith('fox') // true str.search(/quick/i) // 4 — index of match str.match(/brown/) // ['brown']
filter (search subset)
const words = ['apple', 'avocado', 'banana', 'apricot'] // find all that start with 'a' words.filter((w) => w.startsWith('a')) // ['apple', 'avocado', 'apricot'] // filter IS search when you want all matches

Beginner Patterns

Case-Insensitive Search
function search(items: string[], query: string) { const q = query.toLowerCase() return items.filter((item) => item.toLowerCase().includes(q)) } search(['Apple', 'BANANA', 'cherry'], 'an') // ['BANANA']
Search Object Array
interface Product { name: string category: string } function searchProducts(products: Product[], query: string) { const q = query.toLowerCase() return products.filter( (p) => p.name.toLowerCase().includes(q) || p.category.toLowerCase().includes(q) ) }
Search Input
function SearchList({ items }: { items: string[] }) { const [query, setQuery] = useState('') const results = items.filter((item) => item.toLowerCase().includes(query.toLowerCase()) ) return ( <> <input value={query} onChange={(e) => setQuery(e.target.value)} /> <ul>{results.map((r) => <li key={r}>{r}</li>)}</ul> </> ) }
Find or Throw
function getUser(users: User[], id: string): User { const user = users.find((u) => u.id === id) if (!user) throw new Error(`User ${id} not found`) return user } // useful when you know the item must exist
Has Duplicate
function hasDuplicate<T>(items: T[]): boolean { return new Set(items).size !== items.length } hasDuplicate([1, 2, 3]) // false hasDuplicate([1, 2, 2]) // true
Search with Highlight
function Highlight({ text, query }: { text: string; query: string }) { if (!query) return <span>{text}</span> const parts = text.split(new RegExp(`(${query})`, 'gi')) return ( <span> {parts.map((part, i) => part.toLowerCase() === query.toLowerCase() ? <mark key={i}>{part}</mark> : part )} </span> ) }

Intermediate Patterns

Multi-Field Search
function search<T extends Record<string, unknown>>( items: T[], query: string, fields: (keyof T)[] ) { const q = query.toLowerCase() return items.filter((item) => fields.some((f) => String(item[f]).toLowerCase().includes(q)) ) } search(users, 'alice', ['name', 'email'])
Debounced Search
function useDebounce<T>(value: T, ms: number) { const [debounced, setDebounced] = useState(value) useEffect(() => { const id = setTimeout(() => setDebounced(value), ms) return () => clearTimeout(id) }, [value, ms]) return debounced } // usage const [query, setQuery] = useState('') const debounced = useDebounce(query, 300) const results = useMemo(() => search(items, debounced), [items, debounced])
Search with Map Index
// pre-build a lookup map for O(1) access const userMap = new Map(users.map((u) => [u.id, u])) function getUser(id: string) { return userMap.get(id) // instant — no array scan } // vs find which is O(n) every call users.find((u) => u.id === id)
Fuzzy Match (simple)
function fuzzy(str: string, query: string): boolean { let j = 0 const s = str.toLowerCase() const q = query.toLowerCase() for (let i = 0; i < s.length && j < q.length; i++) { if (s[i] === q[j]) j++ } return j === q.length } fuzzy('JavaScript', 'jvs') // true — j...a...v...a...s fuzzy('JavaScript', 'xyz') // false
Binary Search (sorted)
function binarySearch(sorted: number[], target: number): number { let lo = 0 let hi = sorted.length - 1 while (lo <= hi) { const mid = (lo + hi) >>> 1 if (sorted[mid] === target) return mid if (sorted[mid] < target) lo = mid + 1 else hi = mid - 1 } return -1 // not found } // O(log n) — only works on sorted arrays
Search Params Filter
// Next.js — filter from URL search params export default async function Page({ searchParams, }: { searchParams: Promise<{ q?: string }> }) { const { q } = await searchParams const products = await getProducts() const filtered = q ? products.filter((p) => p.name.toLowerCase().includes(q.toLowerCase())) : products return <ProductList products={filtered} /> }

Advanced Patterns

Trie (prefix search)
class Trie { children = new Map<string, Trie>() isEnd = false insert(word: string) { let node: Trie = this for (const ch of word) { if (!node.children.has(ch)) node.children.set(ch, new Trie()) node = node.children.get(ch)! } node.isEnd = true } search(prefix: string): boolean { let node: Trie = this for (const ch of prefix) { if (!node.children.has(ch)) return false node = node.children.get(ch)! } return true // prefix exists } }
Inverted Index
// build a word → items index for fast full-text search function buildIndex(items: { id: string; text: string }[]) { const index = new Map<string, Set<string>>() for (const item of items) { for (const word of item.text.toLowerCase().split(/\W+/)) { if (!index.has(word)) index.set(word, new Set()) index.get(word)!.add(item.id) } } return index } // lookup is O(1) per word const ids = index.get('react') // Set of matching item IDs
Ranked Results
function rankedSearch(items: Product[], query: string) { const q = query.toLowerCase() return items .map((item) => { const name = item.name.toLowerCase() let score = 0 if (name === q) score = 3 // exact match else if (name.startsWith(q)) score = 2 // prefix match else if (name.includes(q)) score = 1 // contains return { item, score } }) .filter((r) => r.score > 0) .sort((a, b) => b.score - a.score) .map((r) => r.item) }
Search with Web Worker
// worker.ts self.onmessage = (e: MessageEvent<{ items: Item[]; query: string }>) => { const { items, query } = e.data const q = query.toLowerCase() const results = items.filter((i) => i.name.toLowerCase().includes(q)) self.postMessage(results) } // component const worker = new Worker(new URL('./worker.ts', import.meta.url)) worker.postMessage({ items, query }) worker.onmessage = (e) => setResults(e.data) // keeps main thread free for large datasets
API Search with Abort
function useSearch(query: string) { const [results, setResults] = useState<Item[]>([]) useEffect(() => { if (!query) { setResults([]); return } const controller = new AbortController() fetch(`/api/search?q=${query}`, { signal: controller.signal }) .then((r) => r.json()) .then(setResults) .catch(() => {}) // abort throws — ignore return () => controller.abort() }, [query]) return results } // abort cancels stale requests when query changes fast
Last updated on