Memoize

What it is

Memoize caches the result of a function call and returns the cached value on subsequent calls.

When to use it

  • Expensive computations with deterministic inputs.
  • Repeated service lookups that rarely change.
  • Avoiding redundant network or database requests within a session.

Async / sync support

WrapperSupport
Func<R>✅ Async
Func1<T, R>✅ Async
Func2<T1, T2, R>✅ Async
FuncSync<R>❌ No

API reference

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// api-reference
// On Func<R>
Func<R> memoize({
  Duration? ttl,
  int maxSize = 100,
  EvictionPolicy evictionPolicy = EvictionPolicy.lru,
});

// On Func1<T, R>
Func1<T, R> memoize({
  Duration? ttl,
  int maxSize = 100,
  EvictionPolicy evictionPolicy = EvictionPolicy.lru,
  AdvancedCache<T, R>? cache,
  int? maxWeight,
  int Function(R result)? weigh,
  bool stampedeProtection = false,
});

// On Func2<T1, T2, R>
Func2<T1, T2, R> memoize({
  Duration? ttl,
  int maxSize = 100,
  EvictionPolicy evictionPolicy = EvictionPolicy.lru,
  AdvancedCache<ArgPair<T1, T2>, R>? cache,
  int? maxWeight,
  int Function(R result)? weigh,
  bool stampedeProtection = false,
});

Parameters

ParameterTypeDescription
ttlDuration?Time-to-live for cached results. null means no expiration.
maxSizeintMaximum entries when using the default cache backend.
evictionPolicyEvictionPolicylru, lfu, or fifo.
cacheAdvancedCache?Optional custom cache backend.
maxWeightint?Total weight budget when using weighted eviction.
weighint Function(R)?Returns the weight of a result.
stampedeProtectionboolCoalesce concurrent loads for the same key.

Returned wrapper methods

1
2
3
4
5
// api-reference
void clear(); // Clears all cached values.
// On Func1 / Func2 only:
void clearArg(T arg);
void clearArgs(T1 arg1, T2 arg2);

Examples

Basic example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var calls = 0;
final compute = Func<int>(() async {
  calls++;
  return 42;
}).memoize();

print(await compute()); // 42
print(await compute()); // 42
// calls == 1
print(calls);

Custom backend and stampede protection

1
2
3
4
5
6
7
8
9
final fetchUser = Func1<String, User>((id) async {
  return User();
}).memoize(
  cache: LruCache<String, User>(maxSize: 100),
  ttl: Duration(minutes: 5),
  stampedeProtection: true,
);

print(await fetchUser('123'));

Weighted eviction

1
2
3
4
5
6
7
8
9
final fetchPage = Func1<String, String>((url) async {
  return '<html>Hello</html>';
}).memoize(
  cache: LruCache<String, String>(maxSize: 1000),
  maxWeight: 1024 * 1024,
  weigh: (html) => html.length,
);

print((await fetchPage('/')).length);

Real-world example

1
2
3
4
5
6
7
final fetchConfig = Func<Config>(() async {
  return await remoteConfig.fetch() as Config;
}).memoize();

void main() async {
  await fetchConfig();
}

Best practices

  • Use memoize for functions whose result does not change during the cache lifetime.
  • Call clear() when you know the cached value is stale.
  • Combine with timeout() if the first call should not hang forever.
  • Enable stampedeProtection for hot keys that are expensive to load.

Common pitfalls

  • Stale data: Without a ttl, cached values persist for the lifetime of the wrapper.
  • Memory leaks: Long-lived wrappers cache results indefinitely; clear them when no longer needed.
  • Weight budget: maxWeight without weigh (or vice versa) triggers an assertion.