Performance audit finding · Severity: Medium (batch) · Effort: Large · Fix risk: Simple per instance · Test safety net: Partial
Owner: per-file CODEOWNERS
Scope: app/selectors/ (51 hits, 14 files)
What is this about?
Parameterized selectors ((_state, arg) => arg input pattern) built with createSelector have a single-entry cache: calls with alternating arguments from different components bust the cache on every call, so the selector recomputes per call site per render despite being "memoized". Sweep results on main (b3e7eb88), hits per file:
Detection recipe: grep -rn "(_state\|(_," app/selectors --include="*.ts"
Technical Details
Per instance, triage in this order: (1) lookup-map selector — memoize one keyed index, consumers key into it (preferred; removes per-arg caching entirely); (2) factory selector instantiated per call site with useMemo; (3) lruMemoize with maxSize (last resort). Instances called from a single call site with a stable argument are fine — close them as no-op with a note.
Acceptance Criteria
- Each file's hits triaged: fixed (with reference-stability test) or explicitly waived (single stable call site).
- No parameterized selector consumed per-row in a list retains a single-entry cache.
References
What is this about?
Parameterized selectors (
(_state, arg) => arginput pattern) built withcreateSelectorhave a single-entry cache: calls with alternating arguments from different components bust the cache on every call, so the selector recomputes per call site per render despite being "memoized". Sweep results onmain(b3e7eb88), hits per file:app/selectors/earnController/earn/index.ts(6)app/selectors/tokensController.ts(5) — worst instance filed separately: Fix single-entry cache thrashing inselectSingleTokenByAddressAndChainId(O(n) scan per call) #31490app/selectors/tokenRatesController.ts(4)app/selectors/networkController.ts(4)app/selectors/currencyRateController.ts(4)app/selectors/smartTransactionsController.ts(3)app/selectors/multisrp/index.ts(3)app/selectors/transactionController.ts(2)app/selectors/tokenSearchDiscoveryDataController.ts(2)app/selectors/multichainAccounts/accounts.ts(2)app/selectors/assets/assets-list.ts(2)app/selectors/accountsController.ts(2) — compound instance filed separately:getMemoizedInternalAccountByAddressruns a deep-equal compare on top of per-argument cache busting #31491app/selectors/transactionPayController.ts(1)app/selectors/snaps/permissionController.ts(1)Detection recipe:
grep -rn "(_state\|(_," app/selectors --include="*.ts"Technical Details
Per instance, triage in this order: (1) lookup-map selector — memoize one keyed index, consumers key into it (preferred; removes per-arg caching entirely); (2) factory selector instantiated per call site with
useMemo; (3)lruMemoizewithmaxSize(last resort). Instances called from a single call site with a stable argument are fine — close them as no-op with a note.Acceptance Criteria
References
mms-performancesweep recipes added in perf: Augmentmms-performancewith frontend learnings from the Extension performance audit skills#49 (mm-state-normalization); pattern catalogued in the extension audit as MetaMask-planning#6484