| title | Memoization |
|---|---|
| description | Multi-layer caching strategy for 530x performance boost |
Complete guide to the multi-layer memoization system in @mcabreradev/filter.
- Overview
- Architecture
- Cache Layers
- Usage Examples
- Performance Benchmarks
- Best Practices
- API Reference
- Troubleshooting
The memoization system provides 3 layers of caching to maximize performance:
┌─────────────────────────────────────────┐
│ Result Cache (WeakMap) │
│ Caches complete filter results (LRU) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Predicate Cache (LRU + TTL) │
│ Memoizes compiled predicate functions │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Regex Cache (LRU + TTL) │
│ Caches compiled regex patterns │
└─────────────────────────────────────────┘
- 530x faster for repeated identical queries
- 605x faster for regex pattern reuse
- Automatic memory management with WeakMap
- LRU eviction prevents memory bloat
- TTL expiration (5 minutes) for predicates
Uses WeakMap to cache complete filter results:
WeakMap<Array, LRUCache<T[]>>Benefits:
- Automatic garbage collection
- No memory leaks
- Limited to 100 entries per array (LRU)
- Zero configuration needed
How it works:
const data = [/* 10,000 items */];
const query = { age: { $gte: 18 } };
filter(data, query, { enableCache: true });LRU cache with TTL for compiled predicate functions:
Map<ExpressionHash, { predicate: Function, timestamp: number }>Configuration:
- Max size: 500 entries
- TTL: 5 minutes (300,000ms)
- Eviction: Least Recently Used
How it works:
filter(data1, { age: { $gte: 18 } }, { enableCache: true });
filter(data2, { age: { $gte: 18 } }, { enableCache: true });LRU cache for compiled regex patterns:
LRUCache<RegExp>Configuration:
- Max size: 500 entries
- TTL: 5 minutes (300,000ms)
- Eviction: Least Recently Used
How it works:
filter(users, { email: { $regex: '^admin@' } });
filter(customers, { email: { $regex: '^admin@' } });
filter(vendors, { email: { $regex: '^admin@' } });import { filter } from '@mcabreradev/filter';
const products = [/* 10,000 products */];
const query = { category: 'Electronics', inStock: true };
const result1 = filter(products, query, { enableCache: true });
const result2 = filter(products, query, { enableCache: true });
console.log(result1 === result2);
console.log(JSON.stringify(result1) === JSON.stringify(result2));When it hits:
- Same array reference
- Same expression (deep equality)
- Same config options
const users1 = [/* 5,000 users */];
const users2 = [/* 3,000 different users */];
const query = { age: { $gte: 18 }, active: true };
filter(users1, query, { enableCache: true });
filter(users2, query, { enableCache: true });When it hits:
- Same expression (even with different arrays)
- Same config options
- Within 5-minute TTL
const pattern = { email: { $regex: '^[a-z]+@example\\.com$' } };
filter(users, pattern);
filter(customers, pattern);
filter(admins, pattern);Always active - no configuration needed!
import { filter, clearFilterCache } from '@mcabreradev/filter';
interface Product {
id: string;
name: string;
category: string;
price: number;
inStock: boolean;
rating: number;
}
class ProductService {
private products: Product[];
constructor(products: Product[]) {
this.products = products;
}
getElectronics(): Product[] {
return filter(
this.products,
{ category: 'Electronics' },
{ enableCache: true }
);
}
getAffordableProducts(): Product[] {
return filter(
this.products,
{ price: { $lte: 100 }, inStock: true },
{ enableCache: true }
);
}
getTopRated(): Product[] {
return filter(
this.products,
{ rating: { $gte: 4.5 } },
{ enableCache: true }
);
}
refreshProducts(newProducts: Product[]): void {
this.products = newProducts;
clearFilterCache();
}
}
const service = new ProductService(products);
service.getElectronics();
service.getAffordableProducts();
service.getTopRated();
service.getElectronics();
service.getAffordableProducts();
service.getTopRated();import { filter, getFilterCacheStats } from '@mcabreradev/filter';
class AnalyticsDashboard {
private data: AnalyticsEvent[];
private cacheEnabled = true;
constructor(data: AnalyticsEvent[]) {
this.data = data;
}
getActiveUsers() {
return filter(
this.data,
{ event: 'page_view', timestamp: { $gte: Date.now() - 3600000 } },
{ enableCache: this.cacheEnabled }
);
}
getConversions() {
return filter(
this.data,
{ event: 'purchase', status: 'completed' },
{ enableCache: this.cacheEnabled }
);
}
getErrors() {
return filter(
this.data,
{ level: { $in: ['error', 'critical'] } },
{ enableCache: this.cacheEnabled }
);
}
getCacheStats() {
return getFilterCacheStats();
}
async refresh() {
this.data = await fetchLatestData();
clearFilterCache();
}
}
const dashboard = new AnalyticsDashboard(events);
setInterval(() => {
dashboard.refresh();
}, 30000);
dashboard.getActiveUsers();
dashboard.getConversions();
dashboard.getErrors();
console.log(dashboard.getCacheStats());import { filter } from '@mcabreradev/filter';
interface SearchFilters {
category?: string;
priceMin?: number;
priceMax?: number;
inStock?: boolean;
rating?: number;
searchTerm?: string;
}
class ProductSearch {
private products: Product[];
constructor(products: Product[]) {
this.products = products;
}
search(filters: SearchFilters): Product[] {
const query: any = {};
if (filters.category) {
query.category = filters.category;
}
if (filters.priceMin !== undefined || filters.priceMax !== undefined) {
query.price = {};
if (filters.priceMin !== undefined) {
query.price.$gte = filters.priceMin;
}
if (filters.priceMax !== undefined) {
query.price.$lte = filters.priceMax;
}
}
if (filters.inStock !== undefined) {
query.inStock = filters.inStock;
}
if (filters.rating !== undefined) {
query.rating = { $gte: filters.rating };
}
if (filters.searchTerm) {
query.name = { $regex: filters.searchTerm, $options: 'i' };
}
return filter(this.products, query, { enableCache: true });
}
}
const search = new ProductSearch(products);
const results1 = search.search({
category: 'Electronics',
priceMin: 100,
priceMax: 500,
inStock: true,
rating: 4.0
});
const results2 = search.search({
category: 'Electronics',
priceMin: 100,
priceMax: 500,
inStock: true,
rating: 4.0
});import { filter } from '@mcabreradev/filter';
interface LogEntry {
timestamp: Date;
level: 'info' | 'warn' | 'error' | 'critical';
message: string;
service: string;
}
class LogAnalyzer {
private logs: LogEntry[];
constructor(logs: LogEntry[]) {
this.logs = logs;
}
findDatabaseErrors(): LogEntry[] {
return filter(this.logs, {
level: { $in: ['error', 'critical'] },
message: { $regex: 'database.*timeout' }
});
}
findAuthenticationIssues(): LogEntry[] {
return filter(this.logs, {
message: { $regex: 'auth.*failed|unauthorized' }
});
}
findAPIErrors(): LogEntry[] {
return filter(this.logs, {
message: { $regex: 'api.*error|http.*[45]\\d{2}' }
});
}
}
const analyzer = new LogAnalyzer(logs);
analyzer.findDatabaseErrors();
analyzer.findDatabaseErrors();
const analyzer2 = new LogAnalyzer(newLogs);
analyzer2.findDatabaseErrors();import { filterFirst, filter } from '@mcabreradev/filter';
const customers = [/* 1,000,000 customers */];
const highValueQuery = {
totalPurchases: { $gte: 10000 },
status: 'active',
lastPurchase: { $gte: thirtyDaysAgo }
};
const top20 = filterFirst(
customers,
highValueQuery,
20,
{ enableCache: true }
);
const top20Again = filterFirst(
customers,
highValueQuery,
20,
{ enableCache: true }
);
const allHighValue = filter(
customers,
highValueQuery,
{ enableCache: true }
);| Dataset Size | First Run | Cached Run | Speedup |
|---|---|---|---|
| 1,000 items | 0.52ms | 0.01ms | 52x |
| 10,000 items | 5.3ms | 0.01ms | 530x |
| 100,000 items | 53ms | 0.01ms | 5,300x |
| Scenario | Without Cache | With Cache | Improvement |
|---|---|---|---|
| Simple operator | 5.3ms | 3.2ms | 40% faster |
| Complex nested | 15.2ms | 9.1ms | 40% faster |
| Multiple arrays | 5.3ms × 3 | 3.2ms × 3 | 40% faster |
| Pattern Type | First Compile | Cached | Speedup |
|---|---|---|---|
| Simple | 12.1ms | 7.8ms | 35% faster |
| Complex | 18.5ms | 11.2ms | 39% faster |
| With flags | 12.8ms | 8.1ms | 37% faster |
| Cache Type | 100 Entries | 500 Entries | 1000 Entries |
|---|---|---|---|
| Result Cache | ~800 bytes | ~4 KB | ~8 KB |
| Predicate Cache | ~20 KB | ~100 KB | ~200 KB |
| Regex Cache | ~15 KB | ~75 KB | ~150 KB |
const products = [/* 50,000 products */];
function getElectronics() {
return filter(products, { category: 'Electronics' }, { enableCache: true });
}
getElectronics();
getElectronics();
getElectronics();const complexQuery = {
$and: [
{ price: { $gte: 100, $lte: 500 } },
{ category: { $in: ['Electronics', 'Computers'] } },
{ rating: { $gte: 4.0 } },
{ name: { $regex: 'pro|premium' } }
]
};
filter(products, complexQuery, { enableCache: true });class DataService {
private data: Item[];
updateData(newData: Item[]) {
this.data = newData;
clearFilterCache();
}
query(expression: Expression) {
return filter(this.data, expression, { enableCache: true });
}
}Note on in-place mutation: the result cache is keyed by the array
reference and is invalidated automatically when the array's length
changes (e.g. push, splice, pop on the same reference). However,
mutating the contents of an element in place (e.g. data[0].active = true)
does not change the array length and can return stale results within the
5-minute TTL — call clearFilterCache() after content-mutating edits, or
pass a fresh array reference.
import { getFilterCacheStats } from '@mcabreradev/filter';
function logCacheStats() {
const stats = getFilterCacheStats();
console.log('Cache Stats:', stats);
if (stats.predicateCacheSize > 400) {
console.warn('Predicate cache is getting full');
}
}
setInterval(logCacheStats, 60000);setInterval(() => {
data = fetchLatestData();
filter(data, query, { enableCache: true });
}, 1000);
setInterval(() => {
data = fetchLatestData();
filter(data, query, { enableCache: false });
}, 1000);function runOnce() {
const result = filter(data, query, { enableCache: true });
return result;
}
function runOnce() {
const result = filter(data, query, { enableCache: false });
return result;
}class BadService {
query(data: Item[]) {
return filter(data, expression, { enableCache: true });
}
}
class GoodService {
query(data: Item[]) {
return filter(data, expression, { enableCache: true });
}
refresh(newData: Item[]) {
this.data = newData;
clearFilterCache();
}
}filter<T>(
array: T[],
expression: Expression<T>,
options?: { enableCache?: boolean; ... }
): T[]Options:
enableCache: boolean- Enable result and predicate caching (default:false)
Example:
filter(data, query, { enableCache: true });clearFilterCache(): voidClears all cache layers:
- Result cache
- Predicate cache
- Regex cache
Example:
import { clearFilterCache } from '@mcabreradev/filter';
clearFilterCache();getFilterCacheStats(): {
predicateCacheSize: number;
regexCacheSize: number;
}Returns current cache statistics.
Example:
import { getFilterCacheStats } from '@mcabreradev/filter';
const stats = getFilterCacheStats();
console.log(`Predicates cached: ${stats.predicateCacheSize}`);
console.log(`Regex patterns cached: ${stats.regexCacheSize}`);Problem: Cache doesn't seem to improve performance
Checklist:
- ✅
enableCache: trueis set - ✅ Same array reference (Result cache)
- ✅ Identical expression structure
- ✅ Same config options
- ✅ Within 5-minute TTL (Predicate cache)
Debug:
const stats = getFilterCacheStats();
console.log('Cache stats:', stats);Problem: Memory usage increases over time
Solution:
setInterval(() => {
clearFilterCache();
}, 300000);
function updateData(newData) {
data = newData;
clearFilterCache();
}Problem: Cache isn't being hit often
Possible causes:
- Expressions are always different
- Array references change frequently
- Config options vary between calls
Solution:
const query = { age: { $gte: 18 } };
filter(data1, query, { enableCache: true });
filter(data1, query, { enableCache: true });
filter(data1, { age: { $gte: 18 } }, { enableCache: true });
filter(data1, { age: { $gte: 18 } }, { enableCache: true });Version: 5.8.3 Last Updated: November 2025