npm version npm downloads License Node.js version Build Status Coverage
A high-performance, lightweight LRU (Least Recently Used) cache for JavaScript with O(1) operations and optional TTL support.
Think of an LRU cache like a limited-size bookshelf. When you add a new book and the shelf is full, you remove the least recently used book to make room. Every time you read a book, it moves to the front. This pattern is perfect for caching where you want to keep the most frequently accessed items.
The tiny-lru library provides:
- O(1) operations for get, set, delete, and has
- Optional TTL (Time-To-Live) support for automatic expiration
- Zero dependencies - pure JavaScript
- 100% test coverage - fully tested and reliable
- TypeScript support - full type definitions included
- ~2.2 KB minified and gzipped (compared to ~12 KB for lru-cache)
npm install tiny-lru
Requires Node.js ≥14 or modern browsers with ES Module support.
import { lru } from "tiny-lru"; // Create a cache that holds up to 100 items const cache = lru(100); // Store and retrieve data cache.set("user:42", { name: "Alice", score: 1500 }); const user = cache.get("user:42"); // { name: "Alice", score: 1500 } // Chain operations cache.set("a", 1).set("b", 2).set("c", 3); // Check what's in the cache cache.has("a"); // true cache.size; // 3 cache.keys(); // ['a', 'b', 'c'] (LRU order)
import { LRU } from "tiny-lru"; interface User { id: number; name: string; } const cache = new LRU<User>(100); cache.set("user:1", { id: 1, name: "Alice" }); const user: User | undefined = cache.get("user:1");
Items can automatically expire after a set time:
// Cache that expires after 5 seconds const sessionCache = lru(100, 5000); sessionCache.set("session:id", { userId: 123 }); // After 5 seconds, this returns undefined sessionCache.get("session:id");
Want TTL to reset when you update an item? Enable resetTtl:
const cache = lru(100, 60000, true); // 1 minute TTL, resets on update cache.set("key", "value"); cache.set("key", "new value"); // TTL resets
Great for:
- API response caching
- Function memoization
- Session storage with expiration
- Rate limiting
- LLM response caching
- Database query result caching
- Any scenario where you want to limit memory usage
Not ideal for:
- Non-string keys (works best with strings)
- Very large caches (consider a database)
Creates a new LRU cache instance with parameter validation.
import { lru } from "tiny-lru"; const cache1 = lru(); // 1000 items, no TTL const cache2 = lru(500); // 500 items, no TTL const cache3 = lru(100, 30000); // 100 items, 30s TTL const cache4 = lru(100, 60000, true); // with resetTtl enabled
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
max |
number |
1000 |
Maximum items. 0 = unlimited. Must be >= 0. |
ttl |
number |
0 |
Time-to-live in milliseconds. 0 = no expiration. Must be >= 0. |
resetTTL |
boolean |
false |
Reset TTL when updating existing items via set() |
Returns: LRU - New cache instance
Throws: TypeError if parameters are invalid
Creates an LRU cache instance without parameter validation.
import { LRU } from "tiny-lru"; const cache = new LRU(100, 5000);
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
max |
number |
0 |
Maximum items. 0 = unlimited. |
ttl |
number |
0 |
Time-to-live in milliseconds. 0 = no expiration. |
resetTTL |
boolean |
false |
Reset TTL when updating via set() |
| Property | Type | Description |
|---|---|---|
first |
object | null |
Least recently used item (node with key, value, prev, next, expiry) |
last |
object | null |
Most recently used item (node with key, value, prev, next, expiry) |
max |
number |
Maximum items allowed |
resetTTL |
boolean |
Whether TTL resets on set() updates |
size |
number |
Current number of items |
ttl |
number |
Time-to-live in milliseconds |
| Method | Description |
|---|---|
cleanup() |
Remove expired items without LRU update. Returns count of removed items. |
clear() |
Remove all items. Returns this for chaining. |
delete(key) |
Remove an item by key. Returns this for chaining. |
entries(keys?) |
Get [key, value] pairs. Without keys: LRU order. With keys: input array order. |
evict() |
Remove the least recently used item. Returns this for chaining. |
expiresAt(key) |
Get expiration timestamp for a key. Returns `number |
forEach(callback, thisArg?) |
Iterate over items in LRU order. Returns this for chaining. |
get(key) |
Retrieve a value. Moves item to most recent. Returns value or undefined. |
getMany(keys) |
Batch retrieve multiple items. Returns object mapping keys to values. |
has(key) |
Check if key exists and is not expired. Returns boolean. |
hasAll(keys) |
Check if ALL keys exist. Returns boolean. |
hasAny(keys) |
Check if ANY key exists. Returns boolean. |
keys() |
Get all keys in LRU order (oldest first). Returns string[]. |
keysByTTL() |
Get keys by TTL status. Returns {valid, expired, noTTL}. |
onEvict(callback) |
Register eviction callback (triggers on evict() or when set()/setWithEvicted() evicts). Returns this for chaining. |
peek(key) |
Retrieve a value without LRU update. Returns value or undefined. |
set(key, value) |
Store a value. Returns this for chaining. |
setWithEvicted(key, value) |
Store value, return evicted item if full. Returns `{key, value, expiry} |
sizeByTTL() |
Get counts by TTL status. Returns {valid, expired, noTTL}. |
stats() |
Get cache statistics. Returns {hits, misses, sets, deletes, evictions}. |
toJSON() |
Serialize cache to JSON format. Returns array of items. |
values(keys?) |
Get all values, or values for specific keys. Returns array of values. |
valuesByTTL() |
Get values by TTL status. Returns {valid, expired, noTTL}. |
function memoize(fn, maxSize = 100) { const cache = lru(maxSize); return function (...args) { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn(...args); cache.set(key, result); return result; }; } // Cache expensive computations const fib = memoize((n) => (n <= 1 ? n : fib(n - 1) + fib(n - 2)), 50); fib(100); // fast - cached fib(100); // even faster - from cache
// Cache instance shared across calls (outside the function) const cache = lru(1000, 60000); // 1 minute cache async function getUser(userId) { // Check cache first const cached = cache.get(`user:${userId}`); if (cached) { return cached; } // Fetch from database const user = await db.users.findById(userId); // Store in cache cache.set(`user:${userId}`, user); return user; }
const cache = lru(3); cache.set("a", 1).set("b", 2).set("c", 3); const evicted = cache.setWithEvicted("d", 4); console.log(evicted); // { key: 'a', value: 1, expiry: 0 } cache.keys(); // ['b', 'c', 'd']
const cache = lru(100); cache.set("users:1", { name: "Alice" }); cache.set("users:2", { name: "Bob" }); cache.set("users:3", { name: "Carol" }); // Get values for specific keys (order matches input array) const values = cache.values(["users:3", "users:1"]); // ['Carol', 'Alice'] - matches input key order
import _ from "lodash"; import { lru } from "tiny-lru"; _.memoize.Cache = lru().constructor; const slowFunc = _.memoize(expensiveOperation); slowFunc.cache.max = 100; // Configure cache size
import { LRU } from "tiny-lru"; class AuthCache { constructor() { // Session cache: 30 minutes with TTL reset on update this.sessions = new LRU(10000, 1800000, true); // Token validation cache: 5 minutes, no reset this.tokens = new LRU(5000, 300000, false); // Permission cache: 15 minutes this.permissions = new LRU(5000, 900000); } cacheSession(sessionId, userData, domain = "app") { const key = `${domain}:session:${sessionId}`; this.sessions.set(key, { userId: userData.userId, permissions: userData.permissions, loginTime: Date.now(), lastActivity: Date.now(), }); } getSession(sessionId, domain = "app") { const key = `${domain}:session:${sessionId}`; return this.sessions.get(key); } }
import { LRU } from "tiny-lru"; class LLMCache { constructor() { // Cache up to 1000 responses for 1 hour this.cache = new LRU(1000, 3600000); // 1 hour TTL } async getResponse(model, prompt, params = {}) { const key = this.generateKey(model, prompt, params); // Check cache first const cached = this.cache.get(key); if (cached) { return { ...cached, fromCache: true }; } // Make expensive API call const response = await this.callLLMAPI(model, prompt, params); // Cache the response this.cache.set(key, { response: response.text, tokens: response.tokens, timestamp: Date.now(), }); return { ...response, fromCache: false }; } generateKey(model, prompt, params = {}) { const paramsHash = this.hashObject(params); const promptHash = this.hashString(prompt); return `llm:${model}:${promptHash}:${paramsHash}`; } hashString(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return Math.abs(hash).toString(36); } hashObject(obj) { return this.hashString(JSON.stringify(obj, Object.keys(obj).sort())); } }
| Feature | tiny-lru | lru-cache | quick-lru |
|---|---|---|---|
| Bundle size | ~2.2 KB | ~12 KB | ~1.5 KB |
| O(1) operations | ✅ | ✅ | ✅ |
| TTL support | ✅ | ✅ | ✅ |
| TypeScript | ✅ | ✅ | ✅ |
| Zero dependencies | ✅ | ❌ | ✅ |
| Pure LRU | ✅ | ❌* | ✅ |
* lru-cache uses a hybrid design that can hold ×ばつ the specified size for performance
All core operations are O(1):
- Set: Add or update items
- Get: Retrieve and promote to most recent
- Delete: Remove items
- Has: Quick existence check
Run our comprehensive benchmark suite to see performance characteristics:
npm run benchmark:all
See benchmarks/README.md for more details.
npm install # Install dependencies npm test # Run lint and tests npm run lint # Lint and check formatting npm run fix # Fix lint and formatting issues npm run build # Build distribution files npm run coverage # Generate test coverage report
Build produces multiple module formats. When you install from npm, you'll get:
dist/tiny-lru.js- ES Modulesdist/tiny-lru.cjs- CommonJStypes/lru.d.ts- TypeScript definitions
The minified version (dist/tiny-lru.min.js) is available in the repository for local testing but is not shipped via npm.
| Metric | Count |
|---|---|
| Tests | 149 |
| Suites | 26 |
| Metric | Coverage |
|---|---|
| Lines | 100% |
| Branches | 99.28% |
| Functions | 100% |
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Run
npm testto ensure all tests pass - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Implement a hierarchical key naming convention to prevent cross-domain data leakage:
{domain}:{service}:{resource}:{identifier}[:{version}]
Example domains:
- User-related:
usr:profile:data:12345 - Authentication:
auth:login:session:abc123 - External API:
api:response:endpoint:hash - Database:
db:query:sqlhash:paramshash - Application:
app:cache:feature:value - System:
sys:config:feature:version - Analytics:
analytics:event:user:session - ML/AI:
ml:llm:response:gpt4-hash
- API Reference - Complete API documentation
- Technical Documentation - Architecture, performance, and security
- Code Style Guide - Contributing guidelines
Copyright (c) 2026, Jason Mulligan
BSD-3-Clause