super_cache
Production LRU and TTL cache for Flutter and Dart, with layered storage and stampede protection.
A caching library for Flutter and Dart that stops duplicate API calls, evicts in O(1), and reports live hit rates. Zero external dependencies in the core.
super_cache is a caching library for Flutter and Dart, published on pub.dev under MIT. The core has no external dependencies and runs on Android, iOS, Web, macOS, Windows and Linux.
It exists because of one specific failure: three widgets asking for the same user at the same moment fire three identical network calls. super_cache pools those into one. The same key in flight once means one request, and every caller waits on the same future.
Around that sit an O(1) LRU with TTL, three cache layers that promote entries upward on a hit, and metrics you can actually read while the app runs.
Key Features
O(1) LRU with TTL
A hash map paired with a doubly linked list keeps get, put and evict at constant time regardless of size. TTL works in absolute or sliding mode per entry, so a session token and a product listing can expire on different rules in the same cache.
Stampede protection
Concurrent reads for a key already in flight share one future instead of each firing its own request. Three widgets mounting at once produce one network call.
Three layers that promote
Memory, an AES-256-GCM encrypted buffer, and file-per-entry disk. A hit on disk promotes the entry up through the layers, so the second read is a memory read.
Policies per call site
CacheAside reads lazily, WriteThrough always fetches fresh, and RefreshAhead serves the stale value while refreshing behind it. The policy is an argument, not a global setting.
Metrics you can watch
Hit rate, eviction count and entry count are available as a snapshot or a broadcast stream, so cache behaviour is observable at runtime rather than inferred from network logs.
Deterministic tests
A companion package ships FakeCache and ManualClock, so TTL expiry and eviction can be tested without sleeping in the test suite.
Tech Stack
Architecture
Three layers sit behind a single orchestrator. L1 is an in-memory LRU, synchronous on a hit. L2 is an AES-256-GCM encrypted in-memory buffer. L3 is file-per-entry disk storage with integrity validation. A read falls through L1 to L2 to L3 and promotes whatever it finds back up, so repeated reads settle into memory.
The API returns FutureOr<V?> rather than always returning a Future. An L1 hit resolves synchronously and never yields to the event loop, which is what keeps a memory read at roughly 0.04 microseconds instead of paying an async hop for a value already in hand.
Eviction can be driven by entry count or, with a sizeEstimator, by bytes. There is also an evictFraction() hook so the app can shed part of the cache when the platform signals memory pressure instead of waiting to be killed.
The core package depends on nothing outside the SDK. Encryption, disk persistence and test doubles live in separate companion packages, so an app that only needs an in-memory LRU does not pull in flutter_secure_storage.
Challenges & Solutions
The hard problems, and how I solved them.
Keeping a memory hit out of the event loop
Being honest about what the encrypted layer protects
More Projects
Interested in working together?
I'm always open to discussing new projects and opportunities.