Skip to content

avoidwork/tiny-lru

Repository files navigation

Tiny LRU

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.

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

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

About

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

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

 

Contributors