Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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).