Performance
Ensure efficient and scalable application performance by following best practices in code execution, resource usage, and architectural decisions.
These rules help reduce latency, improve responsiveness, and optimize throughput for both frontend and backend environments.
Avoid unnecessary computation
Don't repeat heavy or redundant operations — cache results where appropriate, memoize pure functions, and defer non-critical work. Unoptimized loops, calculations, or re-render triggers can drastically impact performance.
1// Called repeatedly in render
2const sorted = data.sort((a, b) => a.value - b.value);
1const sorted = useMemo(() => {
2 return data.sort((a, b) => a.value - b.value);
3}, [data]);
Minimize data transfer
Only send the data the client needs. Use field selection (e.g., GraphQL projections, Prisma select), compression, and pagination to avoid bloated network responses — especially for mobile or low-bandwidth environments.
Avoid
SELECT *
Use compression (
gzip
,brotli
)Stream large data if needed
Debounce or throttle expensive events
For high-frequency events like scroll, resize, or keystrokes, debounce or throttle handlers to reduce execution frequency and UI jank. React apps especially benefit from this on search boxes or live updates.
1const debouncedSearch = useCallback(
2 debounce((term) => fetchResults(term), 300),
3 []
4);
Lazy load non-critical resources
Split large bundles and load non-essential components, images, or libraries only when needed. This reduces initial load time and improves perceived performance.
Load images as user scrolls (
loading="lazy"
)Use dynamic
import()
for optional logic
const SettingsPanel = React.lazy(() => import("./SettingsPanel"));
Batch operations when possible
Group related expensive operations — such as database writes, network calls, or rendering computations — into a single batch to reduce overhead and improve efficiency. Avoid unnecessary round trips or repeated queries.
Note: In React 18+, state updates in the same tick are batched automatically. Manual merging of state is unnecessary unless it improves clarity or models tightly coupled values.
1// API batching:
2await api.post("/bulk-update", { updates });
3
4// Database:
5await prisma.user.createMany({ data: users });
Choose appropriate rendering strategy (CSR/SSR/ISR)
Choose the right rendering model for your app’s needs:
CSR (Client-Side Rendering): good for dynamic SPAs
SSR (Server-Side Rendering): improves SEO, initial load
ISR (Incremental Static Regeneration): combines SSR with caching
SSG (Static Site Generation): fastest, for mostly static content
1// Next.js example
2export async function getStaticProps() {
3 const data = await getData();
4 return { props: { data }, revalidate: 60 };
5}
Avoid memory leaks
Always clean up timers, subscriptions, and event listeners in components or long-lived services. Memory leaks can slow down apps over time and lead to crashes in long sessions or SPA environments.
1// React
2useEffect(() => {
3 const id = setInterval(doSomething, 1000);
4 return () => clearInterval(id);
5}, []);
1// Node
2const handler = () => {};
3emitter.on("data", handler);
4
5// later
6emitter.off("data", handler);
Use indexes in databases wisely
Proper indexes speed up queries significantly, especially for filtering and sorting. However, excessive or incorrect indexing can slow down writes and bloat storage.
Index frequently queried fields
Use compound indexes for multi-field queries
Avoid indexing high-cardinality, frequently updated fields
Optimize and Compress Assets
Reduce the size of images, fonts, and other static assets with modern compression (e.g. WebP, Brotli, SVG). This reduces page load time and bandwidth usage.
Use responsive image sizes
Prefer vector formats (SVG) where possible
Enable asset compression in the web server or CDN
Cache expensive computations or responses
Use caching at the appropriate layer (in-memory, client-side, CDN, or DB) to reduce repeated computation or fetching. This improves latency and reduces load on backend systems.
Cache database query results (e.g. Redis)
Use HTTP caching headers (
ETag
,Cache-Control
)Store derived values in memory (memoization)
Use pagination or infinite scroll for large datasets
Never load massive datasets all at once. Instead, use pagination or infinite scroll to load data in chunks. This improves render speed, responsiveness, and bandwidth usage.
Frontend: show loader when fetching next page and append new items.
GET /api/posts?page=3&limit=10
Avoid unnecessary re-renders
Use React.memo
, useMemo
, and useCallback
to prevent components from re-rendering when props or values haven't changed. Avoid anonymous functions and object literals in props when not needed.
<Component onClick={() => handle()} />
1const memoizedValue = useMemo(() => computeHeavy(val), [val]);
2
3const MemoComponent = React.memo(Component);