Search
Syntax
includes
const items = ['apple', 'banana', 'cherry']
items.includes('banana') // true
items.includes('grape') // false
'hello world'.includes('world') // trueindexOf
const items = ['a', 'b', 'c', 'b']
items.indexOf('b') // 1 — first match
items.indexOf('z') // -1 — not found
items.lastIndexOf('b') // 3 — last matchfind & 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) // undefinedsome & 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) // falseString 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 matchesBeginner 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 existHas Duplicate
function hasDuplicate<T>(items: T[]): boolean {
return new Set(items).size !== items.length
}
hasDuplicate([1, 2, 3]) // false
hasDuplicate([1, 2, 2]) // trueSearch 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') // falseBinary 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 arraysSearch 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 IDsRanked 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 datasetsAPI 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 fastLast updated on