Skip to main content
Career Paths
Concepts
Fsp Performance Optimization
The Simplified Tech

Role-based learning paths to help you master cloud engineering with clarity and confidence.

Product

  • Career Paths
  • Interview Prep
  • Scenarios
  • AI Features
  • Cloud Comparison
  • Resume Builder
  • Pricing

Community

  • Join Discord

Account

  • Dashboard
  • Credits
  • Updates
  • Sign in
  • Sign up
  • Contact Support

Stay updated

Get the latest learning tips and updates. No spam, ever.

Terms of ServicePrivacy Policy

© 2026 TheSimplifiedTech. All rights reserved.

BackBack
Interactive Explainer

Performance Optimization: Finding and Fixing Real Bottlenecks

How to diagnose performance problems with actual data: profiling, flame graphs, database query analysis, memory leak detection, and the optimization patterns that actually move the needle.

🎯Key Takeaways
Profile first, optimize second — measure to find the actual bottleneck
Database queries are the most common bottleneck — pg_stat_statements reveals them
N+1 queries are the most common database performance bug — fix with eager loading or DataLoader
Flame graphs and APM tools reveal CPU bottlenecks — use them before guessing
Bundle optimization: tree-shaking, code-splitting, image optimization — measure with web-vitals
Core Web Vitals (LCP, INP, CLS) directly affect SEO and user satisfaction

Performance Optimization: Finding and Fixing Real Bottlenecks

How to diagnose performance problems with actual data: profiling, flame graphs, database query analysis, memory leak detection, and the optimization patterns that actually move the needle.

~5 min read
Be the first to complete!
What you'll learn
  • Profile first, optimize second — measure to find the actual bottleneck
  • Database queries are the most common bottleneck — pg_stat_statements reveals them
  • N+1 queries are the most common database performance bug — fix with eager loading or DataLoader
  • Flame graphs and APM tools reveal CPU bottlenecks — use them before guessing
  • Bundle optimization: tree-shaking, code-splitting, image optimization — measure with web-vitals
  • Core Web Vitals (LCP, INP, CLS) directly affect SEO and user satisfaction

Lesson outline

Premature optimization vs necessary optimization

Donald Knuth: "Premature optimization is the root of all evil." Most code paths are not hot paths. Adding complexity to optimize code that runs 0.001% of the time is wasted effort and introduces bugs.

The correct approach: measure first, optimize the actual bottleneck. Tools show you the truth: flame graphs reveal that 80% of CPU time is in one function you never suspected. Database slow query logs show the query that runs 10,000 times/minute. Without measurement, you guess.

The 80/20 rule of performance

80% of latency comes from 20% of the code. Profile your application under realistic load, find the actual hot path, and optimize only that. Everything else is noise.

Backend profiling: finding CPU and I/O bottlenecks

Flame graphs: Visual representation of CPU usage over time. Each horizontal bar is a function. Width = time spent. Read from bottom (outermost call) to top (innermost). The plateau at the top = where time is actually spent. Run `node --prof` then `node --prof-process` to generate V8 profiles.

APM tools: Application Performance Monitoring (Datadog, New Relic, Sentry Performance) instrument your app automatically and show transaction-level performance data, broken down by function. Essential in production.

Database slow query log: PostgreSQL `log_min_duration_statement = 100` logs all queries taking > 100ms. Analyze with `pg_stat_statements`. Find: which queries run most often? Which are slowest? Which consume most total time (count × duration)?

Memory leak detection: Node.js `--inspect` + Chrome DevTools heap snapshot. Take heap snapshot, perform actions, take another — compare. Objects that grow indicate a leak. Common culprits: global cache without eviction, event listeners never removed, closure capturing large objects.

profiling-setup.ts
1// Node.js Performance Profiling Setup
2
3// 1. Enable V8 CPU profiler (development/staging)
4// node --prof server.js
5// node --prof-process isolate-*.log > profile.txt
6
7// 2. In-process profiling for specific operations
8import { Session } from 'inspector';
9import fs from 'fs';
10
11async function profileFunction<T>(name: string, fn: () => Promise<T>): Promise<T> {
12 const session = new Session();
13 session.connect();
14
15 await new Promise<void>(resolve =>
16 session.post('Profiler.enable', () => resolve())
17 );
18 await new Promise<void>(resolve =>
19 session.post('Profiler.start', () => resolve())
20 );
21
22 const result = await fn();
23
24 const profile = await new Promise<object>((resolve) =>
25 session.post('Profiler.stop', (err, { profile }) => resolve(profile))
26 );
27
28 fs.writeFileSync(`profile-${name}-${Date.now()}.cpuprofile`, JSON.stringify(profile));
29 session.disconnect();
30 console.log(`Profile saved. Open in Chrome DevTools > Performance`);
31 return result;
32}
33
34// 3. PostgreSQL query analysis
35// Find the 10 most expensive queries (by total time)
36const expensiveQueries = await db.execute(sql`
37 SELECT
38 query,
39 calls,
Focus on total_exec_time (= calls × avg_ms) — a fast query called 1M times is a bottleneck
40 mean_exec_time::numeric(10,2) AS avg_ms,
41 total_exec_time::numeric(10,2) AS total_ms,
42 (total_exec_time / sum(total_exec_time) OVER ()) * 100 AS pct_total
43 FROM pg_stat_statements
44 ORDER BY total_exec_time DESC
Log heap before/after load test — growing heap indicates a memory leak
45 LIMIT 10
46`);
47// Focus optimization on queries with highest total_ms (calls × avg_ms)
48
49// 4. Memory leak detection script
50import v8 from 'v8';
51function logHeapUsage(label: string) {
52 const stats = v8.getHeapStatistics();
53 console.log(`[${label}] heap used: ${(stats.used_heap_size / 1024 / 1024).toFixed(1)} MB`);
54}

Database performance: beyond indexes

N+1 queries: Loop fetches one row, then fetches related rows for each. 100 posts → 1 + 100 queries. Fix: eager loading (JOIN), DataLoader (batch in-app). EXPLAIN ANALYZE reveals N+1: look for many similar queries with high count in pg_stat_statements.

Query plan analysis: `EXPLAIN (ANALYZE, BUFFERS)` shows the actual execution plan. Look for: Seq Scan on large tables (missing index), Nested Loop on large result sets (consider Hash Join), high Buffers hit (good — in memory) vs read (bad — hitting disk).

Connection pool tuning: Pool too small → requests queue waiting for connections → latency spike. Pool too large → PostgreSQL OOMs from too many concurrent queries. Formula: (CPU cores × 2) + disk spindles is a starting point.

Materialized views: Pre-compute expensive aggregations. A dashboard query joining 5 tables with GROUP BY might take 10 seconds. Store the result in a materialized view, refresh every 5 minutes. Dashboard query: 1ms.

performance-patterns.sql
1-- ✅ EXPLAIN ANALYZE: understand your query plan
2EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
3SELECT o.id, o.total, u.email
4FROM orders o
5JOIN users u ON u.id = o.user_id
6WHERE o.status = 'pending'
7 AND o.created_at > NOW() - INTERVAL '7 days'
8ORDER BY o.created_at DESC
9LIMIT 100;
10
11-- Look for in the output:
12-- ❌ Seq Scan → add an index
13-- ❌ Nested Loop with large row estimates → consider Hash Join hint
14-- ✅ Index Scan → index is being used
15-- ✅ Buffers: hit=1000, read=0 → data in memory (good)
16
17-- ✅ Composite index for this query pattern
18CREATE INDEX CONCURRENTLY idx_orders_status_created
CONCURRENTLY: add index without locking the table for writes
19 ON orders(status, created_at DESC)
20 WHERE status = 'pending'; -- Partial index: only pending orders
21
22-- ✅ Materialized view for expensive dashboard aggregation
23CREATE MATERIALIZED VIEW daily_revenue_by_region AS
Materialized view turns a 10-second query into a 1ms lookup
24SELECT
25 DATE_TRUNC('day', created_at) AS day,
26 region,
27 SUM(total) AS revenue,
28 COUNT(*) AS order_count
29FROM orders
30WHERE status = 'completed'
31GROUP BY 1, 2
32WITH DATA;
33
CONCURRENTLY: refresh without locking reads (but requires unique index)
34CREATE INDEX ON daily_revenue_by_region(day, region);
35
36-- Refresh in background (non-blocking for reads, blocking for writes during refresh)
37REFRESH MATERIALIZED VIEW CONCURRENTLY daily_revenue_by_region;
38
39-- ✅ pg_stat_statements: find slow queries
40SELECT query, calls, mean_exec_time, total_exec_time
41FROM pg_stat_statements
42WHERE mean_exec_time > 100 -- Queries averaging > 100ms
43ORDER BY total_exec_time DESC
44LIMIT 20;

Frontend performance: Core Web Vitals and bundle optimization

Core Web Vitals: Google's user-centric performance metrics. LCP (Largest Contentful Paint < 2.5s), INP (Interaction to Next Paint < 200ms), CLS (Cumulative Layout Shift < 0.1). These directly affect SEO ranking and user satisfaction.

Bundle size: Every KB of JavaScript delays TBT (Total Blocking Time). Analyze with `webpack-bundle-analyzer` or `vite-bundle-visualizer`. Common culprits: importing all of lodash instead of specific functions, date-fns vs moment (moment is huge), large icon libraries (import only what you use).

Image optimization: Images are often 80% of page weight. Use WebP/AVIF (50% smaller than JPEG/PNG). Use `<img loading="lazy">` for below-the-fold images. Use `srcset` for responsive images. Next.js `<Image>` handles all of this automatically.

Critical path optimization: Inline critical CSS (above-the-fold styles). Defer non-critical JS (`<script defer>`). Preconnect to third-party origins (`<link rel="preconnect">`). Preload key fonts (`<link rel="preload" as="font">`).

performance-optimizations.tsx
1// ✅ Bundle optimization: tree-shakeable imports
2// ❌ Bad: imports entire lodash (72KB gzipped)
Importing all of lodash adds 72KB gzipped to your bundle
3import _ from 'lodash';
4const debounced = _.debounce(fn, 300);
5
6// ✅ Good: import only what you need (1KB)
7import debounce from 'lodash/debounce';
8const debounced = debounce(fn, 300);
9
10// ✅ Even better: use native or lighter alternatives
11const debounced = (fn: Function, delay: number) => {
12 let timer: NodeJS.Timeout;
13 return (...args: unknown[]) => {
14 clearTimeout(timer);
15 timer = setTimeout(() => fn(...args), delay);
16 };
17};
18
19// ✅ Image optimization in Next.js
20import Image from 'next/image';
21
22function HeroBanner() {
23 return (
24 <Image
priority=true adds <link rel="preload"> — critical for LCP images
25 src="/hero.jpg"
26 alt="Hero banner"
27 width={1200}
28 height={600}
29 priority // Preload: above-the-fold critical image
30 placeholder="blur" // Show blurred placeholder while loading
31 quality={85} // WebP, 85% quality — good balance
32 />
33 );
34}
35
36// ✅ Measure Core Web Vitals in production
37import { onLCP, onINP, onCLS } from 'web-vitals';
Measure real user performance — lab metrics (Lighthouse) often disagree with field data
38
39function reportWebVitals(metric: Metric) {
40 // Send to your analytics
41 fetch('/api/metrics', {
42 method: 'POST',
43 body: JSON.stringify({
44 name: metric.name,
45 value: metric.value,
46 rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
47 url: window.location.href,
48 }),
49 });
50}
51
52onLCP(reportWebVitals);
53onINP(reportWebVitals);
54onCLS(reportWebVitals);
How this might come up in interviews

Performance questions test whether you approach problems empirically or by intuition.

Common questions:

  • How would you diagnose a slow API endpoint?
  • What causes N+1 queries and how do you fix them?
  • Walk me through how you would improve a page that scores poorly on Core Web Vitals.
  • How would you optimize a database query that takes 5 seconds?

Strong answers include:

  • Profiles before proposing solutions
  • Checks database queries first (most common bottleneck)
  • Knows EXPLAIN ANALYZE and pg_stat_statements
  • Measures Core Web Vitals with real user data, not just Lighthouse

Red flags:

  • Proposes adding more servers as the first optimization
  • Optimizes code by inspection without profiling
  • Does not know what a flame graph is
  • Cannot explain how to detect N+1 queries

Quick check · Performance Optimization: Finding and Fixing Real Bottlenecks

1 / 1

Your API p99 latency is 3 seconds. How should you approach optimization?

Key takeaways

  • Profile first, optimize second — measure to find the actual bottleneck
  • Database queries are the most common bottleneck — pg_stat_statements reveals them
  • N+1 queries are the most common database performance bug — fix with eager loading or DataLoader
  • Flame graphs and APM tools reveal CPU bottlenecks — use them before guessing
  • Bundle optimization: tree-shaking, code-splitting, image optimization — measure with web-vitals
  • Core Web Vitals (LCP, INP, CLS) directly affect SEO and user satisfaction

From the books

High Performance Browser Networking — Ilya Grigorik (2013)

Chapter 1: Primer on Latency and Bandwidth

Bandwidth is rarely the bottleneck. Latency — driven by physics (speed of light) and protocol overhead — is. This shapes every optimization: fewer round-trips beats more bandwidth.

Ready to see how this works in the cloud?

Switch to Career Paths for structured paths (e.g. Developer, DevOps) and provider-specific lessons.

View role-based paths

Sign in to track your progress and mark lessons complete.

Discussion

Questions? Discuss in the community or start a thread below.

Join Discord

In-app Q&A

Sign in to start or join a thread.