Practical JavaScript performance tips with TypeScript examples. Learn measurement, algorithmic complexity, caching, and avoiding common bottlenecks.
JavaScript Performance Basics: Measure, Optimize, and Scale
Performance work is part engineering, part discipline. The best results come from measuring first, then optimizing the actual bottleneck. This guide focuses on practical JavaScript performance: algorithmic choices, memory behavior, caching, and safe micro-optimizations. Every section includes TypeScript examples and expected output.
Quick start
Result:
Performance checklist
- Measure first: avoid guessing; confirm with timing.
- Pick the right data structure:
Mapvs object, arrays vs sets. - Reduce algorithmic complexity: O(n log n) can beat O(n^2) by orders of magnitude.
- Cache or memoize repeated work when inputs repeat.
- Avoid excessive allocations in hot loops.
- Batch DOM updates if you are in the browser.
Start with a performance baseline
Performance work should start with a baseline so you can prove improvements later. A baseline can be as simple as a timing measurement in a test script or a small benchmark in your dev environment. What matters most is that you compare the same code path before and after changes.
Measure with simple timers
In production, use real profilers, but local measurements still help validate assumptions. You can use
performance.now() in browsers or Date.now() in any JS runtime.
Result:
The exact number will vary, but the relative change is what matters.
Prefer the simplest algorithm that scales
Big O is not just theory. A slower algorithm can be fine for 100 items but catastrophic for 10,000. Start by estimating your data size and growth rate.
Choose the right algorithm
Small inputs hide slow algorithms. As data grows, complexity dominates everything else.
Result:
The Set version is O(n) while the nested loop is O(n^2).
Data structures matter
Map has fast lookups and avoids prototype pitfalls. Use it for frequent inserts and reads.
Result:
Build strings efficiently
Repeated string concatenation in loops can create many intermediate strings. When you are assembling a known list of
parts, Array.join is often cleaner and faster.
Result:
Debounce expensive work
If an action fires frequently (like typing), debounce to reduce repeated work.
Result:
Avoid extra allocations in hot loops
Creating new arrays or objects inside a tight loop can add pressure on the garbage collector.
Result:
Precompute repeated work
If the same calculation runs on every iteration, move it out of the loop.
Result:
Memoization for expensive work
Memoization trades memory for speed. Use it when the same inputs repeat.
Result:
Reduce work with early exits
Short-circuiting saves time and is easier to reason about.
Result:
Use Sets for membership checks
Membership checks (includes) on arrays are O(n). For frequent lookups, a Set is typically faster.
Result:
Precompute lookup tables
If you repeatedly map codes to labels, a lookup table avoids branching logic.
Result:
Prefer iterative solutions when recursion is deep
Recursive solutions can be elegant but may risk stack overflows for large inputs.
Result:
Batch work and avoid repeated parsing
If data is reused, parse once and reuse. This is especially relevant for JSON and CSV.
Result:
Browser note: avoid layout thrashing
If you are updating the DOM, batch reads and writes. Alternating read -> write -> read can force the browser to
recalculate layout multiple times. Collect the measurements first, then apply updates in one pass. This is less about
code syntax and more about how you structure the work.
Memory awareness
Performance is also about memory. Large arrays, caches, or retained objects can slow down GC. If you cache data, set limits or eviction strategies, and avoid retaining references to unused objects.
Common pitfalls
- Premature optimization: optimizing the wrong part yields no benefit.
- Micro-optimizing too early: readability often wins for non-hot code.
- Ignoring algorithmic complexity: O(n^2) becomes painful fast.
- Over-caching: memoization can blow up memory if inputs are unbounded.
- Benchmarking without control: run multiple times and warm up.
Best practices
- Profile before you optimize.
- Use the simplest solution that meets performance needs.
- Keep data structures consistent across the codebase.
- Prefer clarity until measurements prove otherwise.
- Document why a micro-optimization exists.
FAQ: JavaScript performance
How do I measure performance in JavaScript?
Use simple timers for quick checks, then rely on profilers for real analysis.
Result:
What is the biggest performance win in JavaScript?
Usually an algorithmic change (like switching from O(n^2) to O(n log n)) beats any micro-optimization.
Result:
Is memoization always good?
No. Memoization is great for repeated inputs but can consume a lot of memory for large input ranges.
Result:
Summary
JavaScript performance is about measuring, choosing the right algorithms, and reducing unnecessary work. Start with profiling, then apply targeted changes that you can justify with data.