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)
)
}Regex Search
function regexSearch(items: string[], pattern: string) {
try {
const re = new RegExp(pattern, 'i')
return items.filter((item) => re.test(item))
} catch {
return [] // invalid regex
}
}
regexSearch(['Apple', 'Banana', 'Apricot'], '^a')
// ['Apple', 'Apricot']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]) // trueMatch Positions
function findMatches(text: string, query: string) {
const matches: number[] = []
const lower = text.toLowerCase()
const q = query.toLowerCase()
let i = lower.indexOf(q)
while (i !== -1) {
matches.push(i)
i = lower.indexOf(q, i + 1)
}
return matches
}
findMatches('banana', 'an') // [1, 3]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'])Debounce Function
function debounce<T extends (...args: any[]) => void>(
fn: T,
ms: number
) {
let id: ReturnType<typeof setTimeout>
return ((...args: Parameters<T>) => {
clearTimeout(id)
id = setTimeout(() => fn(...args), ms)
}) as T
}
const search = debounce((q: string) => {
console.log('searching:', q)
}, 300)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..v..S matched in order
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 arraysURL Search Params
function searchFromURL(url: string, items: string[]) {
const { searchParams } = new URL(url)
const q = searchParams.get('q')?.toLowerCase()
if (!q) return items
return items.filter((item) =>
item.toLowerCase().includes(q)
)
}
// https://example.com?q=phone → filters by "phone"
searchFromURL(location.href, products)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 — offload search to a background thread
self.onmessage = (e: MessageEvent<{ items: string[]; query: string }>) => {
const { items, query } = e.data
const q = query.toLowerCase()
const results = items.filter((i) => i.toLowerCase().includes(q))
self.postMessage(results)
}
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url))
worker.postMessage({ items: allItems, query: 'phone' })
worker.onmessage = (e) => console.log(e.data)
// keeps main thread free for large datasetsFetch with Abort
// cancel stale requests when a new search fires
let controller: AbortController | null = null
async function searchAPI(query: string): Promise<Item[]> {
controller?.abort() // cancel previous request
controller = new AbortController()
const res = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
})
return res.json()
}
// each call aborts the previous — only latest resolvesLast updated on