Profile
Back to NewsBack
GitHub Trending 9 min
Reader Mode
avoidwork/tiny-lru: A fast, lightweight LRU (Least Recently Used) cache for JavaScript with O(1) operations and optional TTL support.

avoidwork/tiny-lru: A fast, lightweight LRU (Least Recently Used) cache for JavaScript with O(1) operations and optional TTL support.

10 hours ago

Tiny LRU

npm version</a> npm downloads</a> License</a> Node.js version</a> Build Status</a> Coverage</a>

A high-performance, lightweight LRU (Least Recently Used) cache for JavaScript with O(1) operations and optional TTL support.

What is an LRU Cache?

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)

Installation

npm install tiny-lru

Requires Node.js ≥14 or modern browsers with ES Module support.

Quick Start

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)

TypeScript

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");

With TTL (Time-to-Live)

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

When to Use Tiny LRU

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)

API Reference

Factory Function: lru(max?, ttl?, resetTtl?)

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

Class: new LRU(max?, ttl?, resetTtl?)

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

Properties

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

Methods

| 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 | undefined. | | 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} | null. | | 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}. |

Common Patterns

Memoization

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-Aside Pattern

// 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; }

Finding What Was Evicted

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']

Advanced Usage

Batch Operations with Keys

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

Interop with Lodash

import _ from "lodash";
import { lru } from "tiny-lru";

_.memoize.Cache = lru().constructor;

const slowFunc = _.memoize(expensiveOperation); slowFunc.cache.max = 100; // Configure cache size

Session and Authentication Caching

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); } }

LLM Response Caching

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())); } }

Why Tiny LRU?

| 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 2× the specified size for performance

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

Benchmarks

Run our comprehensive benchmark suite to see performance characteristics:

npm run benchmark:all

See benchmarks/README.md for more details.

Development

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 Output

Build produces multiple module formats. When you install from npm, you'll get:

  • dist/tiny-lru.js - ES Modules
  • dist/tiny-lru.cjs - CommonJS
  • types/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.

Tests

| Metric | Count | | --------- | ----- | | Tests | 149 | | Suites | 26 |

Test Coverage

| Metric | Coverage | | --------- | -------- | | Lines | 100% | | Branches | 99.28% | | Functions | 100% |

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Run npm test to ensure all tests pass
  4. Commit your changes (git commit -m 'Add amazing feature')
  5. Push to the branch (git push origin feature/amazing-feature)
  6. Open a Pull Request

Security

Multi-Domain Key Convention

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

Documentation

License

Copyright (c) 2026, Jason Mulligan

BSD-3-Clause

Chat with me