Back to patterns
Performance Bottleneck
PERFORMANCE
A systematic method for identifying and resolving performance bottlenecks through waterfall diagrams, load time analysis, and resource optimization.
Use Cases
- Load time optimization
- Resource utilization
- Performance monitoring
- Bottleneck identification
Production Implementation
Implementation
// Real-world example: Performance monitoring and optimization toolkit
interface PerformanceMetrics {
fps: number;
memory: {
used: number;
total: number;
};
timing: {
[key: string]: number;
};
resources: Array<{
name: string;
size: number;
loadTime: number;
}>;
}
interface ProfilerOptions {
sampleInterval?: number;
maxDataPoints?: number;
enableMemoryProfiling?: boolean;
logLevel?: 'debug' | 'info' | 'warn' | 'error';
}
class PerformanceProfiler {
private metrics: PerformanceMetrics[];
private sampleInterval: number;
private maxDataPoints: number;
private isRunning: boolean;
private intervalId?: number;
private marks: Map<string, number>;
private resourceObserver?: PerformanceObserver;
private logger: Console;
constructor(options: ProfilerOptions = {}) {
this.metrics = [];
this.sampleInterval = options.sampleInterval || 1000;
this.maxDataPoints = options.maxDataPoints || 100;
this.isRunning = false;
this.marks = new Map();
this.logger = console;
// Initialize performance observers
this.setupObservers();
}
private setupObservers(): void {
// Resource timing observer
try {
this.resourceObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.entryType === 'resource') {
this.logResourceTiming(entry as PerformanceResourceTiming);
}
});
});
this.resourceObserver.observe({
entryTypes: ['resource']
});
} catch (error) {
this.logger.warn('Resource timing API not supported:', error);
}
}
start(): void {
if (this.isRunning) return;
this.isRunning = true;
this.collectMetrics();
this.intervalId = window.setInterval(
() => this.collectMetrics(),
this.sampleInterval
);
}
stop(): void {
if (!this.isRunning) return;
this.isRunning = false;
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
mark(name: string): void {
this.marks.set(name, performance.now());
}
measure(name: string, startMark: string): number {
const start = this.marks.get(startMark);
if (!start) {
throw new Error(`Start mark "${startMark}" not found`);
}
const duration = performance.now() - start;
this.logger.debug(`Measurement "${name}": ${duration.toFixed(2)}ms`);
return duration;
}
private async collectMetrics(): Promise<void> {
try {
const metrics: PerformanceMetrics = {
fps: await this.measureFPS(),
memory: this.getMemoryUsage(),
timing: this.getPageTiming(),
resources: this.getResourceMetrics()
};
this.metrics.push(metrics);
// Maintain max data points
if (this.metrics.length > this.maxDataPoints) {
this.metrics.shift();
}
this.analyzeMetrics(metrics);
} catch (error) {
this.logger.error('Failed to collect metrics:', error);
}
}
private async measureFPS(): Promise<number> {
return new Promise(resolve => {
requestAnimationFrame((t1) => {
requestAnimationFrame((t2) => {
resolve(1000 / (t2 - t1));
});
});
});
}
private getMemoryUsage(): { used: number; total: number } {
// Use performance.memory if available (Chrome only)
const memory = (performance as any).memory;
if (memory) {
return {
used: memory.usedJSHeapSize,
total: memory.totalJSHeapSize
};
}
// Fallback to rough estimation
return {
used: 0,
total: 0
};
}
private getPageTiming(): { [key: string]: number } {
const timing = performance.timing;
return {
loadTime: timing.loadEventEnd - timing.navigationStart,
domReady: timing.domContentLoadedEventEnd - timing.navigationStart,
firstPaint: this.getFirstPaint(),
ttfb: timing.responseStart - timing.navigationStart
};
}
private getFirstPaint(): number {
const paint = performance.getEntriesByType('paint')
.find(entry => entry.name === 'first-paint');
return paint ? paint.startTime : 0;
}
private getResourceMetrics(): Array<{
name: string;
size: number;
loadTime: number;
}> {
return performance.getEntriesByType('resource')
.map(entry => ({
name: entry.name,
size: (entry as PerformanceResourceTiming).encodedBodySize || 0,
loadTime: entry.duration
}));
}
private logResourceTiming(entry: PerformanceResourceTiming): void {
const timing = {
name: entry.name,
type: entry.initiatorType,
size: entry.encodedBodySize,
duration: entry.duration,
// Network timing
dns: entry.domainLookupEnd - entry.domainLookupStart,
tcp: entry.connectEnd - entry.connectStart,
ttfb: entry.responseStart - entry.requestStart,
download: entry.responseEnd - entry.responseStart
};
this.logger.debug('Resource Timing:', timing);
}
private analyzeMetrics(metrics: PerformanceMetrics): void {
// FPS analysis
if (metrics.fps < 30) {
this.logger.warn('Low FPS detected:', {
current: metrics.fps,
threshold: 30
});
}
// Memory analysis
const memoryUsage = (metrics.memory.used / metrics.memory.total) * 100;
if (memoryUsage > 80) {
this.logger.warn('High memory usage:', {
usage: `${memoryUsage.toFixed(1)}%`,
threshold: '80%'
});
}
// Load time analysis
if (metrics.timing.loadTime > 3000) {
this.logger.warn('Slow page load:', {
loadTime: `${metrics.timing.loadTime}ms`,
threshold: '3000ms'
});
}
// Resource analysis
const largeResources = metrics.resources
.filter(r => r.size > 1000000)
.map(r => ({
name: r.name,
size: `${(r.size / 1000000).toFixed(1)}MB`
}));
if (largeResources.length > 0) {
this.logger.warn('Large resources detected:', largeResources);
}
}
getMetricsSummary(): {
averageFPS: number;
peakMemory: number;
slowestResources: Array<{ name: string; loadTime: number }>;
} {
const averageFPS = this.metrics.reduce(
(sum, m) => sum + m.fps,
0
) / this.metrics.length;
const peakMemory = Math.max(
...this.metrics.map(m => m.memory.used)
);
const allResources = this.metrics
.flatMap(m => m.resources)
.sort((a, b) => b.loadTime - a.loadTime)
.slice(0, 5);
return {
averageFPS,
peakMemory,
slowestResources: allResources
};
}
generateReport(): string {
const summary = this.getMetricsSummary();
return `Performance Report
----------------
Average FPS: ${summary.averageFPS.toFixed(1)}
Peak Memory: ${(summary.peakMemory / 1000000).toFixed(1)}MB
Slowest Resources:
${summary.slowestResources
.map(r => `- ${r.name}: ${r.loadTime.toFixed(0)}ms`)
.join('\n')
}`;
}
}
// Example usage: Performance optimization for image loading
class ImageOptimizer {
private observer: IntersectionObserver;
private profiler: PerformanceProfiler;
constructor() {
this.profiler = new PerformanceProfiler();
// Setup intersection observer for lazy loading
this.observer = new IntersectionObserver(
(entries) => this.handleIntersection(entries),
{
rootMargin: '50px',
threshold: 0.1
}
);
}
optimize(imageElement: HTMLImageElement): void {
// Start performance monitoring
this.profiler.mark(`image_start_${imageElement.src}`);
// Add lazy loading
imageElement.loading = 'lazy';
// Observe for viewport entry
this.observer.observe(imageElement);
// Add srcset for responsive images
this.addResponsiveSources(imageElement);
}
private handleIntersection(entries: IntersectionObserverEntry[]): void {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement;
// Load image
img.src = img.dataset.src || img.src;
// Measure load performance
img.onload = () => {
this.profiler.measure(
`image_load_${img.src}`,
`image_start_${img.src}`
);
};
// Stop observing
this.observer.unobserve(img);
}
});
}
private addResponsiveSources(img: HTMLImageElement): void {
// Generate srcset for different viewport sizes
const sizes = [320, 640, 1280, 1920];
const srcset = sizes
.map(size => `${this.getOptimizedUrl(img.src, size)} ${size}w`)
.join(',');
img.srcset = srcset;
img.sizes = '(max-width: 320px) 320px, (max-width: 640px) 640px, 1280px';
}
private getOptimizedUrl(url: string, width: number): string {
// Add image optimization service parameters
return `${url}?width=${width}&quality=80`;
}
}
// Example usage
const profiler = new PerformanceProfiler({
sampleInterval: 1000,
maxDataPoints: 100,
enableMemoryProfiling: true,
logLevel: 'warn'
});
// Start monitoring
profiler.start();
// Optimize images
const imageOptimizer = new ImageOptimizer();
document.querySelectorAll('img').forEach(img => {
imageOptimizer.optimize(img);
});
// Generate report after page load
window.addEventListener('load', () => {
const report = profiler.generateReport();
console.log(report);
});
Code Examples
Common Performance Issues
typescript
// ❌ Common performance issues
class DataGrid {
// Issue 1: Inefficient rendering
render() {
this.items.forEach(item => {
const element = document.createElement('div');
element.innerHTML = item.toString();
this.container.appendChild(element);
});
}
// Issue 2: Memory leaks
attachListeners() {
window.addEventListener('scroll', () => {
this.handleScroll();
});
}
// Issue 3: Blocking operations
processData(items: any[]) {
for (const item of items) {
const result = heavyCalculation(item);
this.results.push(result);
}
}
// Issue 4: Unoptimized images
loadImages() {
this.images.forEach(src => {
const img = new Image();
img.src = src;
this.container.appendChild(img);
});
}
}
Common performance issues include inefficient DOM operations, memory leaks, blocking operations, and unoptimized resource loading.
Optimized Implementation
typescript
// ✅ Optimized implementation
class DataGrid {
// Efficient rendering with DocumentFragment
render() {
const fragment = document.createDocumentFragment();
requestAnimationFrame(() => {
this.items.forEach(item => {
const element = document.createElement('div');
element.textContent = item.toString();
fragment.appendChild(element);
});
this.container.appendChild(fragment);
});
}
// Proper event cleanup
private cleanup: Array<() => void> = [];
attachListeners() {
const handler = this.handleScroll.bind(this);
window.addEventListener('scroll', handler);
this.cleanup.push(() => {
window.removeEventListener('scroll', handler);
});
}
// Non-blocking operations with chunking
async processData(items: any[]) {
const chunkSize = 100;
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
// Process chunk in next tick
await new Promise(resolve => setTimeout(resolve, 0));
const results = chunk.map(heavyCalculation);
this.results.push(...results);
// Update progress
this.updateProgress(i / items.length);
}
}
// Optimized image loading
loadImages() {
const imageLoader = new ImageOptimizer();
this.images.forEach(src => {
const img = new Image();
imageLoader.optimize(img);
img.dataset.src = src; // For lazy loading
this.container.appendChild(img);
});
}
destroy() {
// Clean up event listeners
this.cleanup.forEach(cleanup => cleanup());
this.cleanup = [];
}
}
This implementation uses efficient DOM operations, proper cleanup, non-blocking operations, and optimized resource loading.
Best Practices
- Use performance profiling tools
- Implement efficient DOM operations
- Optimize resource loading
- Handle memory management
- Use non-blocking operations
- Monitor performance metrics
- Implement proper cleanup
Common Pitfalls
- Blocking main thread
- Memory leaks
- Unoptimized resource loading
- Inefficient DOM operations
- Missing performance monitoring
- Poor error handling
- No cleanup implementation
Related Patterns
Promise Chaining
Visualize and debug complex asynchronous flows and promise chains for structured reasoning and step-by-step task completion.
Memory Leaks 101
A systematic approach to identifying and fixing memory leaks through heap snapshot analysis, memory usage visualization, and common leak pattern detection.