or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

development.mdindex.mdmemoization.mdselector-creation.mdselector-creator.mdstructured-selectors.md

memoization.mddocs/

0

# Memoization Strategies

1

2

Multiple memoization implementations optimized for different use cases and performance characteristics.

3

4

## Capabilities

5

6

### lruMemoize

7

8

LRU (Least Recently Used) cache-based memoization with configurable cache size and equality checking.

9

10

```typescript { .api }

11

/**

12

* Creates a memoized function using LRU caching strategy

13

* @param func - Function to memoize

14

* @param options - Configuration options for LRU cache

15

* @returns Memoized function with clearCache method

16

*/

17

function lruMemoize<Args extends readonly unknown[], Return>(

18

func: (...args: Args) => Return,

19

options?: LruMemoizeOptions

20

): ((...args: Args) => Return) & DefaultMemoizeFields;

21

22

interface LruMemoizeOptions<Result = any> {

23

/** Maximum number of results to cache (default: 1) */

24

maxSize?: number;

25

26

/** Function to check equality of cache keys (default: referenceEqualityCheck) */

27

equalityCheck?: EqualityFn;

28

29

/** Function to compare newly generated output value against cached values */

30

resultEqualityCheck?: EqualityFn<Result>;

31

}

32

```

33

34

**Basic Usage:**

35

36

```typescript

37

import { lruMemoize } from "reselect";

38

39

// Simple memoization with default options

40

const memoizedExpensiveFunction = lruMemoize((data) => {

41

return performExpensiveComputation(data);

42

});

43

44

// With custom cache size

45

const memoizedWithLargerCache = lruMemoize(

46

(data) => processData(data),

47

{ maxSize: 10 }

48

);

49

50

// Usage

51

const result = memoizedExpensiveFunction(someData);

52

console.log(memoizedExpensiveFunction.resultsCount()); // 1

53

54

// Call again with same data (should not recompute)

55

const result2 = memoizedExpensiveFunction(someData);

56

console.log(memoizedExpensiveFunction.resultsCount()); // Still 1

57

58

// Call with different data

59

const result3 = memoizedExpensiveFunction(otherData);

60

console.log(memoizedExpensiveFunction.resultsCount()); // 2

61

62

// Reset results count

63

memoizedExpensiveFunction.resetResultsCount();

64

console.log(memoizedExpensiveFunction.resultsCount()); // 0

65

66

// Clear the cache

67

memoizedExpensiveFunction.clearCache();

68

```

69

70

**With Custom Equality Check:**

71

72

```typescript

73

import { lruMemoize } from "reselect";

74

75

// Custom equality check for objects

76

const deepEqualMemoized = lruMemoize(

77

(obj) => transformObject(obj),

78

{

79

maxSize: 5,

80

equalityCheck: (a, b) => JSON.stringify(a) === JSON.stringify(b)

81

}

82

);

83

84

// Custom equality for specific object properties

85

const userMemoized = lruMemoize(

86

(user) => processUser(user),

87

{

88

equalityCheck: (a, b) => a.id === b.id && a.version === b.version

89

}

90

);

91

92

// With result equality check to handle cases where input changes but output is the same

93

const todoIdsMemoized = lruMemoize(

94

(todos) => todos.map(todo => todo.id),

95

{

96

maxSize: 3,

97

resultEqualityCheck: (a, b) =>

98

a.length === b.length && a.every((id, index) => id === b[index])

99

}

100

);

101

```

102

103

### referenceEqualityCheck

104

105

Default equality function used by `lruMemoize` for comparing cache keys.

106

107

```typescript { .api }

108

/**

109

* Reference equality check function (===)

110

* @param a - First value to compare

111

* @param b - Second value to compare

112

* @returns True if values are reference equal

113

*/

114

function referenceEqualityCheck(a: any, b: any): boolean;

115

```

116

117

### weakMapMemoize

118

119

WeakMap-based memoization that automatically garbage collects unused cache entries when objects are no longer referenced.

120

121

```typescript { .api }

122

/**

123

* Creates a memoized function using WeakMap caching strategy

124

* @param func - Function to memoize

125

* @param options - Configuration options for WeakMap cache

126

* @returns Memoized function with clearCache method

127

*/

128

function weakMapMemoize<Args extends readonly unknown[], Return>(

129

func: (...args: Args) => Return,

130

options?: WeakMapMemoizeOptions

131

): ((...args: Args) => Return) & DefaultMemoizeFields;

132

133

interface WeakMapMemoizeOptions<Result = any> {

134

/** Function to compare newly generated output value against cached values */

135

resultEqualityCheck?: EqualityFn<Result>;

136

}

137

```

138

139

**Basic Usage:**

140

141

```typescript

142

import { weakMapMemoize } from "reselect";

143

144

// WeakMap memoization (default for createSelector)

145

const memoizedProcessor = weakMapMemoize((objects) => {

146

return objects.map(obj => processObject(obj));

147

});

148

149

// Check results count

150

const result = memoizedProcessor(someObjects);

151

console.log(memoizedProcessor.resultsCount()); // 1

152

153

// Clear cache and reset count

154

memoizedProcessor.clearCache(); // Also resets results count

155

console.log(memoizedProcessor.resultsCount()); // 0

156

157

// With result equality check

158

const customWeakMapMemoized = weakMapMemoize(

159

(data) => transformData(data),

160

{

161

resultEqualityCheck: (a, b) => a.length === b.length && a.every((item, i) => item.id === b[i].id)

162

}

163

);

164

```

165

166

**Automatic Garbage Collection:**

167

168

```typescript

169

import { weakMapMemoize } from "reselect";

170

171

const processObjects = weakMapMemoize((objectArray) => {

172

return objectArray.map(obj => expensiveTransform(obj));

173

});

174

175

// Objects are automatically garbage collected when no longer referenced

176

let objects = [{ id: 1 }, { id: 2 }];

177

const result1 = processObjects(objects); // Cached

178

179

objects = null; // Original objects can be garbage collected

180

// Cache entries for those objects are automatically cleaned up

181

```

182

183

### unstable_autotrackMemoize

184

185

Experimental auto-tracking memoization using Proxy to track nested field access patterns.

186

187

```typescript { .api }

188

/**

189

* Experimental memoization that tracks which nested fields are accessed

190

* @param func - Function to memoize

191

* @returns Memoized function with clearCache method

192

*/

193

function unstable_autotrackMemoize<Func extends AnyFunction>(

194

func: Func

195

): Func & DefaultMemoizeFields;

196

```

197

198

**Basic Usage:**

199

200

```typescript

201

import { unstable_autotrackMemoize } from "reselect";

202

203

// Auto-tracking memoization

204

const autotrackProcessor = unstable_autotrackMemoize((state) => {

205

// Only recomputes if state.users[0].profile.name changes

206

return state.users[0].profile.name.toUpperCase();

207

});

208

209

// With createSelector

210

import { createSelector } from "reselect";

211

212

const selectUserName = createSelector(

213

[(state) => state.users],

214

(users) => users[0]?.profile?.name, // Tracks specific field access

215

{ memoize: unstable_autotrackMemoize }

216

);

217

```

218

219

**Design Tradeoffs:**

220

221

- **Pros**: More precise memoization, avoids excess calculations, fewer re-renders

222

- **Cons**: Cache size of 1, slower than lruMemoize, unexpected behavior with non-accessing selectors

223

- **Use Case**: Nested field access where you want to avoid recomputation when unrelated fields change

224

225

**Important Limitations:**

226

227

```typescript

228

// This selector will NEVER update because it doesn't access any fields

229

const badSelector = createSelector(

230

[(state) => state.todos],

231

(todos) => todos, // Just returns the value directly - no field access

232

{ memoize: unstable_autotrackMemoize }

233

);

234

235

// This works correctly because it accesses fields

236

const goodSelector = createSelector(

237

[(state) => state.todos],

238

(todos) => todos.map(todo => todo.id), // Accesses .map and .id

239

{ memoize: unstable_autotrackMemoize }

240

);

241

```

242

243

## Performance Comparison

244

245

### When to Use Each Strategy

246

247

**lruMemoize:**

248

- Multiple argument combinations need caching

249

- Predictable cache eviction behavior needed

250

- Working with primitive values or need custom equality logic

251

- Default choice for most use cases

252

253

**weakMapMemoize:**

254

- Working primarily with object references

255

- Want automatic garbage collection

256

- Large numbers of different object combinations

257

- Memory efficiency is important

258

259

**unstable_autotrackMemoize:**

260

- Accessing specific nested fields in large objects

261

- Want to avoid recomputation when unrelated fields change

262

- Can accept cache size limitation of 1

263

- Performance testing shows it's beneficial for your use case

264

265

### Usage Examples in Selectors

266

267

```typescript

268

import {

269

createSelector,

270

createSelectorCreator,

271

lruMemoize,

272

weakMapMemoize,

273

unstable_autotrackMemoize

274

} from "reselect";

275

276

// LRU memoization for selectors with multiple cache entries

277

const createLRUSelector = createSelectorCreator({

278

memoize: lruMemoize,

279

memoizeOptions: { maxSize: 50 }

280

});

281

282

const selectFilteredItems = createLRUSelector(

283

[selectItems, selectFilters],

284

(items, filters) => applyFilters(items, filters)

285

);

286

287

// WeakMap memoization (default)

288

const selectProcessedUsers = createSelector(

289

[selectUsers],

290

(users) => users.map(user => processUser(user))

291

);

292

293

// Auto-tracking for nested field access

294

const createAutotrackSelector = createSelectorCreator({

295

memoize: unstable_autotrackMemoize

296

});

297

298

const selectSpecificUserData = createAutotrackSelector(

299

[(state) => state],

300

(state) => ({

301

name: state.users.currentUser.profile.displayName,

302

avatar: state.users.currentUser.profile.avatar.url

303

})

304

);

305

```

306

307

## Types

308

309

```typescript { .api }

310

interface DefaultMemoizeFields {

311

/** Clears the memoization cache */

312

clearCache: () => void;

313

/** Returns the number of times the memoized function has computed results */

314

resultsCount: () => number;

315

/** Resets the results count to 0 */

316

resetResultsCount: () => void;

317

}

318

319

type EqualityFn<T = any> = (a: T, b: T) => boolean;

320

321

type AnyFunction = (...args: any[]) => any;

322

323

interface Cache {

324

get(key: unknown): unknown | typeof NOT_FOUND;

325

put(key: unknown, value: unknown): void;

326

getEntries(): Array<{ key: unknown; value: unknown }>;

327

clear(): void;

328

}

329

```