Code Optimization with Claude Sonnet 3.5

After exploring advanced pair programming techniques, let’s see how to use Claude to optimize the performance of our code. Unlike traditional profiling tools, Claude can suggest context-aware optimizations based on recurring patterns and best practices.
Performance Analysis
Here’s a real conversation with Claude about optimizing a REST endpoint:
Dev: My /users endpoint is getting slower as the number of users grows. Here's the code:
@Get('/users')
async getUsers(@Query() filters: UserFilters) {
const users = await this.userRepository.find({
where: filters,
relations: ['posts', 'comments', 'profile']
});
return users.map(user => ({
...user,
postCount: user.posts.length,
commentCount: user.comments.length
}));
}
Claude: The code has several areas that can be optimized:
1. You're loading all the relations even though you only need the counts. We can use subqueries for the counts:
@Get('/users')
async getUsers(@Query() filters: UserFilters) {
const users = await this.userRepository
.createQueryBuilder('user')
.select([
'user',
'(SELECT COUNT(*) FROM posts WHERE posts.userId = user.id) as postCount',
'(SELECT COUNT(*) FROM comments WHERE comments.userId = user.id) as commentCount'
])
.where(filters)
.getMany();
return users;
}
This significantly reduces the data load and improves performance. Do you want me to add pagination and caching too?
Dev: Yes, show me how to implement both
Claude: Here's the optimized version with pagination and caching:
@Get('/users')
@CacheKey('users-list')
@CacheTTL(300) // 5 minutes
async getUsers(
@Query() filters: UserFilters,
@Query('page') page = 1,
@Query('limit') limit = 10
) {
const cacheKey = `users-${page}-${limit}-${JSON.stringify(filters)}`;
const cached = await this.cacheManager.get(cacheKey);
if (cached) {
return cached;
}
const [users, total] = await this.userRepository
.createQueryBuilder('user')
.select([
'user',
'(SELECT COUNT(*) FROM posts WHERE posts.userId = user.id) as postCount',
'(SELECT COUNT(*) FROM comments WHERE comments.userId = user.id) as commentCount'
])
.where(filters)
.skip((page - 1) * limit)
.take(limit)
.getManyAndCount();
const result = {
users,
meta: {
total,
page,
limit,
pages: Math.ceil(total / limit)
}
};
await this.cacheManager.set(cacheKey, result);
return result;
}
Dev: How do I handle cache invalidation when a user is modified?
Claude: Here's how to handle cache invalidation intelligently:
@EventSubscriber()
class UserCacheInvalidator {
@OnEvent('user.updated')
@OnEvent('user.created')
@OnEvent('user.deleted')
async invalidateCache(payload: UserEvent) {
// Invalidate only the keys related to the specific user
const keys = await this.cacheManager.store.keys('users-*');
const userKeys = keys.filter(key =>
key.includes(`userId-${payload.userId}`)
);
await Promise.all(
userKeys.map(key => this.cacheManager.del(key))
);
// Also invalidate the main list
await this.cacheManager.del('users-list');
}
}
Memory Leaks and Debugging
Claude excels at spotting potential memory leaks. Here’s an example:
Dev: I have a memory leak in this WebSocket service:
@WebSocketGateway()
export class NotificationGateway {
private clients = new Map();
handleConnection(client: Socket) {
const userId = client.handshake.query.userId;
this.clients.set(userId, client);
}
handleDisconnect(client: Socket) {
// TODO: cleanup
}
}
Claude: The problem is that you're not removing disconnected clients from the Map. Here's the corrected version:
@WebSocketGateway()
export class NotificationGateway {
private clients = new Map();
handleConnection(client: Socket) {
const userId = client.handshake.query.userId;
this.clients.set(userId, client);
// Automatic cleanup
client.on('disconnect', () => {
this.clients.delete(userId);
});
}
// Add monitoring
@Interval(60000) // every minute
async cleanupStaleConnections() {
for (const [userId, client] of this.clients.entries()) {
if (!client.connected) {
this.clients.delete(userId);
}
}
}
}
Also add logging to track active connections:
@Inject(Logger)
private logger: Logger;
handleConnection() {
// ...
this.logger.debug(
`Active connections: ${this.clients.size}`,
'NotificationGateway'
);
}
Query Optimization
Claude can analyze query execution plans and suggest optimizations:
Dev: This query is getting slow:
SELECT p.*,
c.name as category_name,
COUNT(r.id) as review_count,
AVG(r.rating) as avg_rating
FROM products p
LEFT JOIN categories c ON c.id = p.category_id
LEFT JOIN reviews r ON r.product_id = p.id
WHERE p.price > 100
GROUP BY p.id, c.name
ORDER BY avg_rating DESC;
Claude: The query can be optimized in several ways:
1. Using composite indexes:
CREATE INDEX idx_product_price ON products(price) INCLUDE (category_id);
CREATE INDEX idx_reviews_product ON reviews(product_id) INCLUDE (rating);
2. Optimized query:
WITH product_reviews AS (
SELECT product_id,
COUNT(*) as review_count,
AVG(rating) as avg_rating
FROM reviews
GROUP BY product_id
)
SELECT p.*,
c.name as category_name,
COALESCE(pr.review_count, 0) as review_count,
pr.avg_rating
FROM products p
LEFT JOIN categories c ON c.id = p.category_id
LEFT JOIN product_reviews pr ON pr.product_id = p.id
WHERE p.price > 100
ORDER BY pr.avg_rating DESC NULLS LAST;
The CTE pre-aggregates the review data, reducing the number of joins needed. NULLS LAST improves the ordering for products with no reviews.
Dev: Can I see the execution plan before and after?
Claude: Here's the analysis of the execution plans:
Original query:
- Seq Scan on reviews
- Hash Join with products
- Sort by avg_rating
- Group By with sort
Estimated cost: 1245.67
Optimized query:
- Index Scan on products (using idx_product_price)
- Index Scan on reviews (using idx_reviews_product)
- Hash Join with CTE
- Sort only on the final result
Estimated cost: 456.89
The optimization reduces the cost by 63%. Do you want us to analyze the actual response times as well?
Conclusion
Optimizing with Claude requires an iterative conversation. Always provide concrete metrics and real use cases to get more relevant suggestions. In the next article we’ll see how to use Claude for web development.