DEV Community

Rizwan Saleem
Rizwan Saleem

Posted on

How to optimize application performance — a hands on tutorial

How to optimize application performance — a hands on tutorial

Performance Optimization: Measure Before You Optimize

Donald Knuth's famous warning-"premature optimization is the root of all evil"-boils down to one principle: measure before optimizing. Without real-world evidence that your application is too slow, you're guessing where to fix problems, and guesses create the wrong solutions.

The Golden Rule: Establish a Baseline First

Before touching any code, establish baseline metrics:

  • Load time (FCP, LCP, TTI)
  • Bundle size
  • Query durations
  • Memory usage
  • CPU utilization

Profile the system to locate hot spots, identify root causes, then apply one targeted optimization at a time. After each change, re-measure and document the impact.

Using Profilers to Identify Bottlenecks

How Profilers Work

Performance profilers measure how long routines take to execute, how often they're called, where they're called from, and what percentage of total time is spent there. There are two main types:

Type How It Works Best For Limitation
Instrumenting Inserts timing code at routine start/end Precise timing, call traces Overhead affects short routines
Sampling Interrupts CPU at intervals, records execution point Finding bottlenecks in frequently-called code Approximate timing, no call trace

For accurate analysis, use a combination of profilers.

Popular Profilers by Language

Language Tool Type
Python cProfile + snakeviz Function-level
Python line_profiler Line-level
Python viztracer Timeline
React React DevTools Profiler Component render times
TensorFlow TensorBoard Profiler Host/device breakdown
Browser Chrome DevTools Performance CPU, paint, layout, network

React Profiling Example

The React Profiler reveals:

  • Component render times
  • Re-render counts
  • Commit durations
  • Which interactions cause renders

How to use it:

  1. Open Chrome DevTools → Profiler tab
  2. Click "Record" and interact with your app
  3. Review the flamegraph (visualizes render cost) and ranked view (shows slowest components) ### Common Bottleneck Patterns

1. Unnecessary Re-renders (Frontend)

Components re-render when props change-even if the data is identical. This is the most common React performance issue.

Fix: Use React.memo, useCallback, and useMemo wisely:

// Prevents re-render when props haven't changed
export const UserCard = React.memo(({ name, age }) => (
  <div><h3>{name}</h3><p>{age}</p></div>
));

// Memoizes event handler to prevent child re-renders
const handleClick = useCallback(() => {
  console.log("Clicked");
}, []);

// Memoizes expensive computation
const sortedUsers = useMemo(() => {
  return users.sort((a, b) => a.name.localeCompare(b.name));
}, [users]);
Enter fullscreen mode Exit fullscreen mode

2. N+1 Query Problem (Database)

Fetching related data in loops causes exponential query growth:

// ❌ BAD: N+1 queries
const posts = await db.post.findMany();
const postsWithAuthors = posts.map(post => 
  db.author.findUnique({ where: { id: post.authorId } })
);

// ✅ GOOD: Single batched query
const postsWithAuthors = await db.post.findMany({
  include: { author: true }
});
Enter fullscreen mode Exit fullscreen mode

3. Input-Bound ML Pipelines

In TensorFlow, devices sit idle waiting for data. The Input Pipeline Analyzer identifies this:

  • Red area in step-time graph = device idle time waiting for input
  • Green area = device actually working

Fix: Use tf.data.AUTOTUNE for parallelization and prefetching.

4. Large JavaScript Bundles

Loading the entire app upfront delays Time to Interactive (TTI). Tools like Lighthouse and WebPageTest highlight large bundles.

Optimization Techniques

Caching

Cache results of expensive operations to avoid recomputation:

// Memoize repeated blocks
const expensiveResult = useMemo(() => computeExpensive(data), [data]);

// Cache partials where possible
// Avoid inline DB calls in templates
Enter fullscreen mode Exit fullscreen mode

Use long-term caching for static chunks-this reduces load on repeat visits.

Lazy Loading

Delay loading code until the user actually needs it:

// React lazyloading with Suspense
const SettingsPanel = React.lazy(() => import("./SettingsPanel"));

export function App() {
  return (
    <Suspense fallback={<div>Loading…</div>}>
      <SettingsPanel />
    </Suspense>
  );
}
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Reduced initial bundle size
  • Faster TTI
  • Load components only when needed

Lazy loading is recommended even with browser caching because it improves initial load time and resource efficiency.

Bundle Optimization

Technique When to Use
Route-based splitting SPAs-each page loads only what's necessary
Code splitting Split early, review often as project grows
Preloading Assets needed immediately
Prefetching Assets needed soon (next page)
Keep chunks small Avoid excessive network requests

Prefer route-based splitting for Single Page Applications-this ensures each page loads only what's necessary.

Database Query Optimization

  • Use batched or parameterized queries in middleware/API routes, not templates
  • Add indexes on frequently queried columns
  • Use EXPLAIN to analyze query plans
  • Avoid SELECT *-fetch only needed columns
  • Implement query result caching for read-heavy workloads

Frontend Rendering Optimization

Strategy Impact
Virtualization (react-window) Huge gains for long lists
Stable keys (use ID, not index) Prevents unnecessary re-renders
Shallow component trees Reduces render propagation
Debouncing/throttling Prevents excessive event handling
SSR/Streaming (Next.js, Remix) Faster FCP, better SEO

Use virtualization for huge lists-render only what's visible in the viewport.

// Virtualized list example
import { FixedSizeList } from 'react-window';

<FixedSizeList
  height={600}
  itemCount={10000}
  itemSize={35}
  width="100%"
>
  {({ index, style }) => <ListItem index={index} style={style} />}
</FixedSizeList>
Enter fullscreen mode Exit fullscreen mode

Real-World Examples

FinTech Platform Optimization (DATAFOREST Case Study)

A financial services company faced performance degradation with processes stuck in queues. The team:

  1. Performed technical audit of AWS infrastructure
  2. Created bottleneck monitoring system
  3. Re-developed inefficient SQL queries and data pipelines
  4. Implemented horizontal scaling with Docker/Kubernetes

Result: Increased application performance, stability, and resilience while reducing operational costs.

Agricultural Tech Dashboard (30 MHz Case Study)

Over 4 months with 13 interventions:

  • 98.37% reduction in First Contentful Paint (desktop)
  • 97.56% reduction on mobile
  • 48.25% improvement in Speed Index (desktop)

Interventions included code splitting, caching, image optimization, and database query improvements.

React App Optimization

One developer profiled a React app and found:

  • Excessive re-renders in a user list component
  • Large bundle due to unused lodash functions
  • N+1 queries when fetching posts with authors

Fixes applied:

  • Added React.memo to list items
  • Replaced lodash with native Array.prototype methods (reduced bundle by 68KB)
  • Converted to batched queries

Result: TTI improved from 4.2s to 1.8s.

When to Stop Optimizing

Optimization has diminishing returns. Stop when:

  1. Metrics meet user expectations: Core Web Vitals are in the "good" range (LCP < 2.5s, FID < 100ms, CLS < 0.1)
  2. ROI drops: The next optimization takes 10 hours for a 2% improvement
  3. Readability suffers: Code becomes unmaintainable
  4. You've hit the bottleneck ceiling: The limiting factor is now network latency or third-party services, not your code

Key principle: Prefer high-impact fixes (code-splitting, caching, indexing) over micro-optimizations.

Make one change at a time and measure its isolated effect-this tells you what actually worked.

Optimization Workflow Checklist

  1. Measure baseline (load time, bundle size, query duration)
  2. Profile to find real bottleneck (not where you guess it is)
  3. Identify root cause (input-bound, re-renders, N+1 queries)
  4. Apply one targeted fix
  5. Re-measure and document impact
  6. Verify tests pass and functionality intact
  7. Integrate monitoring into CI/CD (Lighthouse CI, bundle size tracking)

Performance optimization isn't about isolated tweaks-it's a continuous, iterative process.

Key Takeaways

Principle Why It Matters
Measure before optimizing Guessing creates wrong fixes
Profile to find real bottleneck 80% of time spent in 20% of code
One change at a time Isolates what actually worked
High-impact > micro-optimizations Code-splitting beats loop unrolling
Monitor continuously Performance degrades over time

Mastering these techniques is one of the most impactful skills you can develop as an engineer.


Rizwan Saleem — https://rizwansaleem.co

Top comments (0)