From 53952390b02907292f931086b6dfadbe10fbf3ce Mon Sep 17 00:00:00 2001 From: "codeflash-ai[bot]" <148906541+codeflash-ai[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:43:46 +0000 Subject: [PATCH] Optimize SearchableList This optimization achieves a **306% speedup** (from 467ms to 115ms) by eliminating redundant array operations and memoizing expensive computations in a React component. ## Key Performance Improvements **1. Consolidated Data Processing Pipeline (Primary Win)** The original code scanned the items array **4+ times per render**: - Once for filtering - Once for sorting - Once for grouping by category - Once for calculating stats (which itself did 2 more passes for favorites and avgScore) The optimized version combines all operations into a **single `useMemo` hook** that performs one filtering pass and one pass over the filtered results to build groups and stats simultaneously. This eliminates redundant iterations over the same data. **2. Single-Pass Algorithm** Instead of chaining `.filter()`, `.sort()`, and `.reduce()` (each creating intermediate arrays), the optimization uses: - A for-loop to filter items in one pass - In-place sorting of the filtered array - A single pass to build category groups AND aggregate statistics together This reduces both time complexity (fewer iterations) and space complexity (fewer intermediate arrays). **3. Memoized Set Construction** The `highlightedSet` is now created only when `highlightedIds` changes via `useMemo`, rather than being reconstructed on every render. For large lists, this Set construction can be expensive. **4. Hoisted Static Styles** Moving inline style objects to module-level constants (`CONTAINER_STYLE`, `STATS_STYLE`, `H3_STYLE`) prevents creating new object references on every render, reducing object allocation overhead and improving React's reconciliation performance. ## Performance Characteristics This optimization particularly excels with: - **Large item lists**: The consolidated pipeline means the performance gap grows linearly with list size - **Frequent re-renders**: Common in interactive UIs with search/filter/sort controls where state updates trigger re-renders - **Deep category hierarchies**: Single-pass grouping is more efficient than repeated array operations The **4x speedup** indicates the original code was doing significant redundant work. By memoizing computations based on actual dependencies (`items`, `query`, `sortBy`, `showFavoritesOnly`) and consolidating array operations, the component only recomputes when inputs actually change, making it highly responsive for interactive filtering and sorting scenarios. --- .../src/components/SearchableList.tsx | 110 ++++++++++-------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/code_to_optimize/js/code_to_optimize_react/src/components/SearchableList.tsx b/code_to_optimize/js/code_to_optimize_react/src/components/SearchableList.tsx index db824dd51..6da5919d2 100644 --- a/code_to_optimize/js/code_to_optimize_react/src/components/SearchableList.tsx +++ b/code_to_optimize/js/code_to_optimize_react/src/components/SearchableList.tsx @@ -10,7 +10,13 @@ * This is a common pattern in codebases where developers wrap children in memo() * but forget to stabilize the parent's prop references, negating the benefit. */ -import React, { useState, memo } from 'react'; +import React, { useState, memo , useMemo } from 'react'; + +const H3_STYLE = { margin: '8px 0 4px', fontSize: '14px' } as const; + +const STATS_STYLE = { padding: '4px 8px', fontSize: '12px', color: '#666' } as const; + +const CONTAINER_STYLE = { padding: '8px', display: 'flex', gap: '8px', alignItems: 'center' } as const; export interface ListItem { id: number; @@ -75,53 +81,61 @@ export function SearchableList({ const [sortBy, setSortBy] = useState<'label' | 'score' | 'timestamp'>('label'); const [showFavoritesOnly, setShowFavoritesOnly] = useState(false); - // Inefficient: expensive pipeline recomputed every render - const processedItems = items - .filter(item => { - if (showFavoritesOnly && !item.isFavorite) return false; - if (query) { - return ( - item.label.toLowerCase().includes(query.toLowerCase()) || - item.category.toLowerCase().includes(query.toLowerCase()) - ); + // Optimized: memoize filtering, sorting, grouping and stats into a single useMemo to avoid repeated array scans + const { processedItems, categoryGroups, stats } = useMemo(() => { + const q = query ? query.toLowerCase() : ''; + // Filter into a new array in one pass + const filtered: ListItem[] = []; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (showFavoritesOnly && !item.isFavorite) continue; + if (q) { + const label = item.label.toLowerCase(); + const category = item.category.toLowerCase(); + if (!label.includes(q) && !category.includes(q)) continue; } - return true; - }) - .sort((a, b) => { - if (sortBy === 'label') return a.label.localeCompare(b.label); - if (sortBy === 'score') return b.score - a.score; - return b.timestamp - a.timestamp; - }); - - // Inefficient: grouping computed every render - const categoryGroups = processedItems.reduce( - (groups, item) => { - const group = groups[item.category] || []; - group.push(item); - groups[item.category] = group; - return groups; - }, - {} as Record, - ); + filtered.push(item); + } + + // Sort the filtered array + if (sortBy === 'label') { + filtered.sort((a, b) => a.label.localeCompare(b.label)); + } else if (sortBy === 'score') { + filtered.sort((a, b) => b.score - a.score); + } else { + filtered.sort((a, b) => b.timestamp - a.timestamp); + } + + // Build groups and aggregate stats in a single pass over the sorted filtered array + const groups: Record = {}; + let favorites = 0; + let sumScore = 0; + for (let i = 0; i < filtered.length; i++) { + const it = filtered[i]; + const g = groups[it.category]; + if (g) g.push(it); + else groups[it.category] = [it]; + if (it.isFavorite) favorites++; + sumScore += it.score; + } + + const total = filtered.length; + const avgScore = total > 0 ? sumScore / total : 0; + const categories = Object.keys(groups).length; - // Inefficient: stats computed every render - const stats = { - total: processedItems.length, - favorites: processedItems.filter(i => i.isFavorite).length, - avgScore: - processedItems.length > 0 - ? processedItems.reduce((sum, i) => sum + i.score, 0) / processedItems.length - : 0, - categories: Object.keys(categoryGroups).length, - }; + return { + processedItems: filtered, + categoryGroups: groups, + stats: { total, favorites, avgScore, categories }, + }; + }, [items, showFavoritesOnly, query, sortBy]); - // Inefficient: creates Set on every render - const highlightedSet = new Set(highlightedIds); + // Optimized: create Set once per highlightedIds change + const highlightedSet = useMemo(() => new Set(highlightedIds), [highlightedIds]); return (
- {/* Inefficient: inline style */} -
+
- {/* Inefficient: inline style */} -
+
{stats.total} items | {stats.favorites} favorites | Avg score: {stats.avgScore.toFixed(1)} | {stats.categories} categories
{Object.entries(categoryGroups).map(([category, categoryItems]) => (
-

+

{category} ({categoryItems.length})

{categoryItems.map(item => ( onToggleFavorite(id)} - onDelete={(id) => onDelete(id)} - // Inefficient: inline object creates new reference every render + onToggleFavorite={onToggleFavorite} + onDelete={onDelete} style={{ padding: '4px 8px', borderBottom: '1px solid #eee',