parallelFilter
Signature: parallelFilter<T>(data: T[], predicate: (item: T) => boolean): T[] — imported from perry/thread.
Filters an array in parallel across all available CPU cores. Returns a new array containing only the elements where the predicate returned a truthy value. Order is preserved.
Basic Usage
function parallelFilterBasic(): void {
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const evens = parallelFilter(numbers, (x: number) => x % 2 === 0)
// [2, 4, 6, 8, 10]
console.log(`parallel-filter-basic len=${evens.length}`)
}
How It Works
Input: [a, b, c, d, e, f, g, h] (8 elements, 4 CPU cores)
Core 1: [a, b] → test → [a] (b filtered out)
Core 2: [c, d] → test → [c, d] (both kept)
Core 3: [e, f] → test → [] (both filtered out)
Core 4: [g, h] → test → [h] (g filtered out)
Output: [a, c, d, h] (concatenated in original order)
Each core independently tests its chunk of elements. Results are merged in the original element order after all threads complete.
Why Not Just Use .filter()?
Regular .filter() runs on a single thread. For large arrays with expensive predicates, parallelFilter distributes the work:
function parallelFilterVsFilter(): void {
const data = [1, 2, 3, 4, 5, 6, 7, 8]
// Single-threaded — one core does all the work
const a = data.filter((item: number) => item > 3)
// Parallel — all cores share the work
const b = parallelFilter(data, (item: number) => item > 3)
console.log(`parallel-filter-vs-filter ${a.length} ${b.length}`)
}
The tradeoff: parallelFilter has overhead from copying values between threads. Use it when the predicate is expensive enough to justify that cost.
Capturing Variables
Like parallelMap, the predicate can capture outer variables. Captures are deep-copied to each thread:
function parallelFilterCapture(): void {
const candidates = [
{ name: "Alice", score: 90, age: 28 },
{ name: "Bob", score: 80, age: 35 },
{ name: "Carol", score: 95, age: 25 },
]
const minScore = 85
const maxAge = 30
// minScore and maxAge are captured and copied to each thread
const qualified = parallelFilter(candidates, (c: { name: string; score: number; age: number }) => {
return c.score >= minScore && c.age <= maxAge
})
console.log(`parallel-filter-capture len=${qualified.length}`)
}
Mutable variables cannot be captured — the compiler rejects this at compile time.
Examples
Filtering Large Datasets
function parallelFilterLarge(): void {
// Stand-in for "millions of records" — same shape, smaller list.
const transactions = [
{ amount: 15000, country: "US", user: { homeCountry: "DE" }, timestamp: { hour: 4 } },
{ amount: 200, country: "DE", user: { homeCountry: "DE" }, timestamp: { hour: 12 } },
{ amount: 50000, country: "FR", user: { homeCountry: "DE" }, timestamp: { hour: 3 } },
]
const suspicious = parallelFilter(transactions, (tx: {
amount: number
country: string
user: { homeCountry: string }
timestamp: { hour: number }
}) => {
return tx.amount > 10000
&& tx.country !== tx.user.homeCountry
&& tx.timestamp.hour < 6
})
console.log(`parallel-filter-large len=${suspicious.length}`)
}
Combined with parallelMap
function parallelFilterCombined(): void {
const users = [
{ name: "Alice", isActive: true, age: 28, score: 90 },
{ name: "Bob", isActive: false, age: 35, score: 80 },
{ name: "Carol", isActive: true, age: 17, score: 95 },
{ name: "Dave", isActive: true, age: 40, score: 60 },
]
// Step 1: Filter to relevant items (parallel)
const active = parallelFilter(users, (u: { isActive: boolean; age: number }) => u.isActive && u.age >= 18)
// Step 2: Transform the filtered results (parallel)
const profiles = parallelMap(active, (u: { name: string; score: number }) => ({
name: u.name,
score: u.score * 2,
}))
console.log(`parallel-filter-combined active=${active.length} profiles=${profiles.length}`)
}
Predicate with Heavy Computation
function parallelFilterHeavy(): void {
const certificates = [
{ id: 1, fingerprint: "aa", revoked: false },
{ id: 2, fingerprint: "bb", revoked: true },
{ id: 3, fingerprint: "cc", revoked: false },
]
// Each predicate call does significant work — perfect for parallelization.
const valid = parallelFilter(certificates, (cert: { id: number; fingerprint: string; revoked: boolean }) => {
// Stand-in for a real chain verification: hash the fingerprint a bit
// then sanity-check the revocation flag.
let h = 0
for (let i = 0; i < cert.fingerprint.length; i++) {
h = (h * 31 + cert.fingerprint.charCodeAt(i)) >>> 0
}
return h !== 0 && !cert.revoked
})
console.log(`parallel-filter-heavy len=${valid.length}`)
}
Performance
Use parallelFilter when:
- The array has many elements (hundreds or more)
- The predicate function does meaningful work per element
- You need to keep the UI responsive during filtering
For trivial predicates on small arrays, regular .filter() is faster (no threading overhead).