Skip to content

Performance Analysis from the Terminal

Your API averaged 120ms response times three months ago. Today it is 1.8 seconds. Nobody changed anything obvious — no single commit shows a smoking gun. The product team is asking why the dashboard feels sluggish. The infrastructure team already doubled the server count and it made no difference. You need to find the bottleneck, but profiling tools have a learning curve and you need answers this afternoon.

  • A Claude Code workflow for profiling Node.js, Python, and database performance from the terminal without installing dedicated APM tools
  • Copy-paste prompts that identify N+1 queries, unindexed table scans, memory leaks, and oversized bundles in your codebase
  • A systematic approach to performance optimization: measure, identify, fix, verify

Before optimizing anything, you need data. Claude Code can instrument your application to collect timing data without requiring an APM vendor.

After Claude generates the instrumentation, run it against a few representative requests:

Terminal window
PERF_TRACE=1 node dist/index.js &
curl http://localhost:3000/api/users
curl http://localhost:3000/api/orders?limit=100
curl http://localhost:3000/api/dashboard

Then feed the output back to Claude Code:

Here are the performance logs from three API endpoints. Identify which endpoint is slowest, which phase (database, external API, business logic) is the bottleneck, and suggest specific optimizations. Show me the exact queries or function calls that need attention.

This feedback loop — instrument, measure, analyze, fix — is the core workflow. Claude Code handles the tedious instrumentation while you focus on understanding the results.

The most common performance problem is the database. Slow queries, missing indexes, and N+1 patterns account for the majority of backend latency.

Claude Code reads your ORM model definitions, migration files, and route handlers to build a complete picture. For example, it might find:

// Before: N+1 -- one query per order to fetch the user
const orders = await db.query('SELECT * FROM orders WHERE status = $1', ['pending']);
for (const order of orders) {
const user = await db.query('SELECT * FROM users WHERE id = $1', [order.userId]);
order.user = user;
}
// After: Single query with JOIN
const orders = await db.query(`
SELECT o.id, o.total, o.status, u.name, u.email
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = $1
`, ['pending']);

After fixing queries, ask Claude Code to generate the missing indexes:

Based on the queries in our codebase, generate a migration file that adds indexes for all columns used in WHERE clauses, JOIN conditions, and ORDER BY clauses that do not already have indexes. Use CREATE INDEX CONCURRENTLY to avoid locking the table.

For endpoints that are slow but the database queries look fine, the problem is often in the business logic — data transformation, serialization, or unnecessary computation.

Profile our /api/dashboard endpoint. It currently takes 3 seconds. Trace the execution path from the route handler through every function it calls. For each function, estimate the time complexity based on the data structures used. Identify any: 1) nested loops over large arrays, 2) synchronous operations that should be async, 3) repeated computation that could be cached, 4) unnecessary data fetching (fields computed but never sent to the client).

Claude Code will trace through the call chain and flag patterns like:

  • A map inside a filter inside another map that could be a single pass
  • A function that fetches user preferences on every request when they change once a day
  • JSON serialization of a massive object when the client only uses three fields

Backend performance is only half the story. If your JavaScript bundle is 2 MB, the browser is doing heavy lifting before the user sees anything.

If your project uses webpack or Vite, Claude Code can also generate the analysis configuration:

Add webpack-bundle-analyzer (or rollup-plugin-visualizer for Vite) to our build and generate a treemap HTML report. Also add a CI check that fails if the main bundle exceeds 200 KB gzipped.

Memory leaks are notoriously hard to find because they manifest slowly. Claude Code can review your code for common leak patterns without requiring a heap dump.

Review our Node.js application for memory leak patterns. Check for: 1) event listeners added in request handlers but never removed, 2) closures that capture large objects and prevent garbage collection, 3) caches (Maps, arrays) that grow unbounded without eviction, 4) streams that are not properly closed on error, 5) module-level state that accumulates per-request data. For each finding, show the leak pattern and the fix.

For active leak investigation, Claude Code can generate a diagnostic script:

Create a diagnostic endpoint at /debug/memory (only accessible from localhost) that returns: process.memoryUsage(), the count of active event listeners on the global event bus, the sizes of all in-memory caches, and active timer/interval counts. Also create a script that hits this endpoint every 30 seconds and writes the data to a CSV file so we can graph memory growth over time.

After optimizing, you need to verify the improvement under realistic load. Claude Code can generate load test scripts.

Generate a k6 load test script for our API that simulates realistic traffic: 70% reads (GET /api/products with pagination), 20% writes (POST /api/orders with a realistic order payload), 10% search (GET /api/search?q=random_term). Ramp up from 10 to 200 virtual users over 5 minutes, hold for 10 minutes, then ramp down. Assert that p95 response time stays under 500ms and error rate stays under 0.1%.

Run the test before and after optimization to quantify the improvement. Feed the results back to Claude Code for analysis:

Here are the k6 results from before and after our optimization. Summarize the improvement in a table showing p50, p95, p99 latency, requests per second, and error rate. Identify any endpoints that did not improve and suggest next steps.

The instrumentation itself slows down the app. Wrapping every function with timing code adds overhead. Use the PERF_TRACE toggle to keep it off in production. For always-on monitoring, use sampling: “Update the instrumentation to only measure 1% of requests in production, selected randomly.”

Claude suggests an index but the migration locks the table. On large tables, CREATE INDEX can lock writes for minutes. Always use CREATE INDEX CONCURRENTLY in PostgreSQL. Ask Claude Code: “This table has 50 million rows and serves write traffic. Generate the index creation as a concurrent, non-locking migration.”

The N+1 fix introduces a massive JOIN. Sometimes JOINing five tables is worse than five small queries, especially with large result sets. After Claude generates the optimization, run EXPLAIN ANALYZE on the new query and paste the output back: “Here is the EXPLAIN ANALYZE output for the optimized query. Is this plan efficient or should we use a different strategy like a subquery or materialized view?”

Bundle splitting makes initial load slower due to waterfall requests. Aggressive code splitting can create too many small chunks that load sequentially. Tell Claude your constraint: “We want at most 5 chunks on the critical path. Merge the route-level chunks for the three most visited pages into the main bundle and only lazy-load the rest.”