# quick-lru: A JavaScript Cache That Trades Exact Size for Speed

> quick-lru is an MIT licensed JavaScript cache built on a dual Map design that avoids the delete operations a strict LRU needs, at the cost of holding up to twice the configured number of items. It suits Node developers who cache aggressively and would rather have the throughput than an exact size bound.

**sindresorhus/quick-lru** — Simple “Least Recently Used” (LRU) cache

- Repository: https://github.com/sindresorhus/quick-lru
- Stars: 770 · Forks: 64
- Language: JavaScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/sindresorhus-quick-lru

## The use case is a cache with a memory ceiling

quick-lru is a simple Least Recently Used cache, and the readme states the reason it exists in one line: it is useful when you need to cache something and limit memory usage. The package description is the same, and the repository is a single index.js with a type definition and a test file, which is a good sign about how little code sits between you and the cache. The instance is a Map subclass, so it is iterable over key and value pairs and can be used directly in a for...of loop, and both keys and values can be of any type, which is a difference from the string-keyed caches in older Map implementations. That combination of Map inheritance and arbitrary key types is what lets the same class serve as a lookup table and as an LRU.

## Installing it and putting the first entry in

The install is a single npm command.

```sh
npm install quick-lru
```

Usage is a constructor with a maxSize, then the familiar set, has and get.

```js
import QuickLRU from 'quick-lru';

const lru = new QuickLRU({maxSize: 1000});

lru.set('🦄', '🌈');

lru.has('🦄');
//=> true

lru.get('🦄');
//=> '🌈'
```

The package is an ES module with type module in the manifest, requires Node 18 or newer, and is marked sideEffects false so a bundler can drop it when nothing is imported. The published files are only index.js and index.d.ts, so the whole thing you install is a single implementation file and a type definition.

## Why the cache can hold twice what you asked for

The one behaviour that surprises people is documented in a note rather than buried. This package uses an algorithm that maintains between maxSize and 2 times maxSize items for performance reasons, so the cache may temporarily contain up to twice the specified size due to the dual-cache design that avoids expensive delete operations. If you configure maxSize 1000, you should assume up to 2000 items can be resident. That is not a bug to be reported but the central design trade-off, and the readme states it twice, once in the options and once in the trade-offs section, where the cost is spelled out as memory overhead of up to twice the target memory compared with strict LRU implementations. The alternative section says it plainly: consider alternatives when you need strict memory limits of exactly maxSize items, or when memory usage matters more than performance.

## The dual Map algorithm, step by step

The implementation is a variant of the hashlru algorithm using JavaScript's Map for broader key type support, and it uses two Map objects. The new cache stores recently accessed items and the old cache stores less recently accessed ones. On a set operation, if the key exists in the new cache it is updated there, otherwise the pair is added to the new cache, and when the new cache reaches maxSize it is promoted to become the old cache and a fresh new cache is created. On a get operation, a key in the new cache is returned directly, and a key found in the old cache is moved into the new cache, which promotes its recency. The benefit is that no expensive delete is ever needed, since the whole old cache is dropped at once when the new one fills. The stated benefits are avoiding delete operations that cause performance issues in JavaScript engines, no complex linked list management, and LRU semantics at much higher speed than traditional implementations.

## Expiry is lazy, and the per-item override

The maxAge option is the maximum number of milliseconds an item should remain in the cache, it defaults to Infinity, and by default items never expire. Expiration is lazy, occurring on the next write or read call, and an individual item can be given its own expiry through the set method's options, falling back to the global maxAge in the constructor if one was given. The expiresIn method reports the remaining time to live in milliseconds for a key, or undefined if the key is not in the cache, and it has three documented behaviours worth knowing: it does not mark the item as recently used, it does not trigger lazy expiration or remove the entry when it is already expired, and it returns Infinity if the item has no expiration. It may also return a negative number for an item that has expired but has not yet been lazily removed, which is a small detail that matters if you use the value in a comparison.

## The eviction callback and what it does not cover

The onEviction option takes a function of key and value, and it is called right before an item is evicted from the cache due to LRU pressure, TTL expiration, or manual eviction through the evict method. The readme suggests it for side effects or for items like object URLs that need explicit cleanup through revokeObjectURL, which is a real use case in browser code where a cached blob URL that is never revoked is a memory leak the cache itself would otherwise hide. The note attached to it is the important part: the callback is not called for manual removals through delete or clear, only for automatic evictions and for manual evictions through evict. So if you rely on the callback to release a resource, deleting an entry from the map will silently skip it and you have to release the resource yourself.

## The rest of the API, and the methods kept for Map compatibility

The instance methods cover the set of operations you would expect. set returns the instance, get and has do what their names say, and peek gets an item without marking it as recently used, which is the method to reach for when you want to check a value without disturbing the eviction order. delete returns true if the item was removed and false if it did not exist, clear deletes all items, and resize updates maxSize, discarding items as necessary, with insertion order mostly preserved though not as a strong guarantee, which is what makes it usable for on-the-fly tuning of cache sizes in live systems. The evict method removes the least recently used items, with a count parameter that defaults to 1, and it always keeps at least one item in the cache. The iteration methods come in ascending and descending recency variants, and entries and forEach exist for Map compatibility, with the documentation telling you to prefer entriesAscending.

## Building and testing it

The project is small enough that the tooling fits on one line, with the test script running xo for linting, nyc for coverage and ava for the tests, followed by tsd for the type definitions.

```bash
xo && nyc ava && tsd
```

The type tests are the interesting part, since the package ships index.d.ts and index.test-d.ts, so the type-level contract is checked rather than assumed. The test file is test.js and the type test is index.test-d.ts, with the implementation in index.js. The coverage status is tracked through codecov, and the package is published under the MIT licence with funding through GitHub Sponsors. The release history shows steady work rather than bursts, with v7.1.0 in August 2025, v7.2.0 in September 2025 and v7.3.0 in October 2025, and the last push on 2026-09-18. A related project from the same author is yocto-queue, a tiny queue data structure, which is worth a look if your bottleneck is the producer side rather than the cache.

## Conclusion

quick-lru fits a JavaScript or Node developer who caches hot objects and needs get and set to stay cheap under load, and who can accept a cache that may hold twice its configured size. It is the wrong choice if you need a hard memory bound, since the dual-cache design gives up exact eviction for throughput, and it is the wrong choice for a cache where items must expire promptly rather than lazily. Before you set maxSize, work out the memory of the largest item you intend to store, since the overshoot applies to item count rather than to bytes.

## FAQ

### Why can quick-lru hold more items than maxSize?

The dual-cache algorithm maintains between maxSize and 2 times maxSize items for performance reasons, because promoting the whole new cache avoids the expensive delete operations that a strict LRU needs in JavaScript. The overshoot is temporary and is the documented trade-off.

### How does quick-lru expire items?

Lazily, on the next write or read call. The maxAge option sets the maximum milliseconds an item stays in the cache and defaults to Infinity, and an individual item can be given its own expiry through the options argument of the set method.

### When is the onEviction callback called?

Right before an item is evicted due to LRU pressure, TTL expiration, or manual eviction through evict(). It is not called for manual removals through delete or clear, so resources such as object URLs released that way have to be handled yourself.

### What Node version does quick-lru need, and how is it installed?

Node 18 or newer, and it installs with npm install quick-lru. The package is an ES module marked sideEffects false, and only index.js and index.d.ts are published.

### How do I check an item without changing the eviction order?

Use the peek method, which gets an item without marking it as recently used. The expiresIn method is the other non-mutating read, and it also does not trigger lazy expiration or remove an expired entry.

## Sources

- [Issues](https://github.com/sindresorhus/quick-lru/issues)
- [License: MIT](https://github.com/sindresorhus/quick-lru/blob/main/LICENSE)
- [README](https://github.com/sindresorhus/quick-lru/blob/main/README.md)
- [Releases](https://github.com/sindresorhus/quick-lru/releases)
- [sindresorhus/quick-lru on GitHub](https://github.com/sindresorhus/quick-lru)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sindresorhus-quick-lru
