Skip to content

Performance Benchmarks

Benchmark Environment

  • Node.js: v20.10.0
  • CPU: Apple M1 Pro (10 cores)
  • RAM: 16 GB
  • Dataset Size: 10,000 objects (unless specified)
  • Iterations: 1,000 runs per test
  • Tool: Vitest benchmark

Filter Strategy Comparison

Small Dataset (100 items)

StrategyTime (ms)Ops/secRelative
Simple string0.0520,0001.00x (baseline)
Object match0.0616,6671.20x
Operator ($eq)0.0714,2861.40x
Predicate function0.1010,0002.00x
Wildcard (%)0.156,6673.00x

Medium Dataset (1,000 items)

StrategyTime (ms)Ops/secRelative
Simple string0.452,2221.00x
Object match0.521,9231.16x
Operator ($eq)0.581,7241.29x
Predicate function0.951,0532.11x
Wildcard (%)1.427043.16x

Large Dataset (10,000 items)

StrategyTime (ms)Ops/secRelative
Simple string4.22381.00x
Object match4.82081.14x
Operator ($eq)5.31891.26x
Predicate function8.91122.12x
Wildcard (%)13.5743.21x

Operator Performance

Comparison Operators (10,000 items)

OperatorTime (ms)Ops/sec
$eq5.3189
$ne5.4185
$gt5.5182
$gte5.5182
$lt5.6179
$lte5.6179
Range ($gte + $lte)6.2161

Array Operators (10,000 items)

OperatorTime (ms)Ops/sec
$in (3 items)6.8147
$in (10 items)8.2122
$nin (3 items)6.9145
$contains7.1141
$size5.8172

String Operators (10,000 items)

OperatorTime (ms)Ops/sec
$startsWith7.2139
$endsWith7.3137
$contains7.5133
$regex (simple)12.183
$regex (complex)18.554

Logical Operators (10,000 items)

OperatorTime (ms)Ops/sec
$and (2 conditions)8.5118
$and (5 conditions)12.381
$or (2 conditions)7.8128
$or (5 conditions)10.298
$not6.1164

Lazy Evaluation Performance

Early Exit Optimization

OperationStandardLazyImprovement
Find first match (position 10)4.2ms0.04ms105x faster
Find first 10 matches4.2ms0.42ms10x faster
Check existence (match at position 5)4.2ms0.02ms210x faster
Check existence (no match)4.2ms4.2msSame (full scan)

Memory Usage

Dataset SizeStandardLazySavings
1,000 items1.2 MB12 KB99% less
10,000 items12 MB12 KB99.9% less
100,000 items120 MB12 KB99.99% less
1,000,000 items1.2 GB12 KB99.999% less

Memoization Strategy

Multi-Layer Caching Architecture

The filter library implements a sophisticated multi-layer memoization strategy:

  1. Result Cache - Caches complete filter results using WeakMap
  2. Predicate Cache - Memoizes compiled predicate functions (LRU with TTL)
  3. Regex Cache - Caches compiled regex patterns
  4. Expression Hash - Stable hashing for cache key generation

Cache Performance Impact

OperationWithout CacheWith CacheSpeedup
First run (10K items)5.3ms5.3ms1.0x (baseline)
Second run (same query)5.3ms0.01ms530x
Regex pattern (first)12.1ms12.1ms1.0x
Regex pattern (cached)12.1ms0.02ms605x
Complex nested (first)15.2ms15.2ms1.0x
Complex nested (cached)15.2ms0.01ms1520x

Cache Configuration

typescript
// Enable all caching layers
filter(data, expression, { enableCache: true });

// Clear all caches manually
import { clearFilterCache } from '@mcabreradev/filter';
clearFilterCache();

// Get cache statistics
import { getFilterCacheStats } from '@mcabreradev/filter';
const stats = getFilterCacheStats();
console.log(stats); // { predicateCacheSize: 45, regexCacheSize: 12 }

LRU Cache Settings

  • Max Size: 500 predicates, 1000 regex patterns
  • TTL: 5 minutes (300,000ms)
  • Eviction: Least Recently Used (LRU)

Memory Usage

Cache TypeMemory per EntryMax Memory
Result Cache~8 bytes (WeakMap ref)Unlimited*
Predicate Cache~200 bytes~100 KB
Regex Cache~150 bytes~150 KB

*WeakMap automatically garbage collects when arrays are no longer referenced

Best Practices

Enable caching for:

  • Large datasets (>1000 items)
  • Repeated identical queries
  • Complex expressions with regex
  • Read-heavy workloads

Disable caching for:

  • Frequently changing data
  • One-time queries
  • Memory-constrained environments
  • Unique expressions every time

Cache Invalidation

typescript
// Automatic: WeakMap clears when array is garbage collected
let data = [/* large dataset */];
filter(data, query, { enableCache: true });
data = null; // Cache entry automatically cleared

// Manual: Clear all caches
clearFilterCache();

// TTL: Predicate cache entries expire after 5 minutes

Configuration Impact

Case Sensitivity

caseSensitiveTime (ms)Relative
false (default)7.21.00x
true5.80.81x (faster)

Insight: Case-insensitive matching adds ~20% overhead due to .toLowerCase() calls.

Max Depth

maxDepthTime (ms)Relative
14.51.00x
3 (default)5.21.16x
56.11.36x
108.31.84x

Insight: Each additional depth level adds ~10-15% overhead for nested objects.

Real-World Scenarios

typescript
// Scenario: Filter 10,000 products
const query = {
  category: { $in: ['Electronics', 'Accessories'] },
  price: { $gte: 100, $lte: 500 },
  inStock: true,
  rating: { $gte: 4.0 }
};

// Results:
// - Time: 12.5ms
// - Throughput: 800 products/sec
// - Matches: ~350 products

User Management

typescript
// Scenario: Filter 50,000 users
const query = {
  $and: [
    { active: true },
    { $or: [{ role: 'admin' }, { permissions: { $contains: 'manage' } }] },
    { lastLogin: { $gte: thirtyDaysAgo } }
  ]
};

// Results:
// - Time: 45ms
// - Throughput: 1,111 users/sec
// - Matches: ~2,500 users

Log Analysis

typescript
// Scenario: Filter 1,000,000 log entries
const query = {
  level: { $in: ['error', 'critical'] },
  timestamp: { $gte: yesterday, $lte: today },
  message: { $regex: 'database.*timeout' }
};

// Standard filter:
// - Time: 2,500ms
// - Memory: 1.2 GB

// Lazy filter (first 100):
// - Time: 15ms (167x faster)
// - Memory: 120 KB (10,000x less)

Optimization Recommendations

1. Choose the Right Strategy

typescript
// ✅ Best for exact matches
filter(data, { id: 123 });

// ✅ Best for ranges
filter(data, { age: { $gte: 18, $lte: 65 } });

// ✅ Best for complex logic
filter(data, (item) => customComplexLogic(item));

2. Use Lazy Evaluation for Large Datasets

typescript
// ❌ Slow: Process all 1M records
const results = filter(millionRecords, query);
displayFirst10(results);

// ✅ Fast: Only process what's needed
const results = filterFirst(millionRecords, query, 10);
displayFirst10(results);

3. Enable Caching for Repeated Queries

typescript
// ✅ Good for repeated identical queries
filter(data, query, { enableCache: true });

4. Optimize Operator Order

typescript
// ✅ Fast checks first
filter(products, {
  $and: [
    { inStock: true },
    { price: { $lte: 100 } },
    { name: { $regex: 'pattern' } }
  ]
});

5. Avoid Deep Nesting

typescript
// ⚠️ Slower
filter(deeplyNested, query, { maxDepth: 10 });

// ✅ Faster
filter(flatStructure, query, { maxDepth: 2 });

Benchmark Methodology

Setup

typescript
import { bench, describe } from 'vitest';
import { filter } from '@mcabreradev/filter';

const data = Array.from({ length: 10000 }, (_, i) => ({
  id: i,
  name: `Item ${i}`,
  price: Math.random() * 1000,
  category: ['A', 'B', 'C'][i % 3],
  inStock: i % 2 === 0
}));

describe('Filter Performance', () => {
  bench('simple string match', () => {
    filter(data, 'Item 5000');
  });

  bench('operator query', () => {
    filter(data, { price: { $gte: 500 } });
  });

  bench('complex query', () => {
    filter(data, {
      $and: [
        { category: { $in: ['A', 'B'] } },
        { price: { $gte: 100, $lte: 500 } },
        { inStock: true }
      ]
    });
  });
});

Running Benchmarks

bash
pnpm run bench

Continuous Monitoring

Benchmarks are run automatically in CI/CD on every PR to detect performance regressions.

Regression Threshold: 10% slowdown triggers a warning.

See Also

Released under the MIT License.