Back to Articles

Deep Dive into JavaScript Time Precision: An Advanced Guide from Milliseconds (ms) to Microseconds (μs)

As a frontend or Node.js developer, we frequently need to measure time: animation frame rates, network request latency, code execution performance... Most people immediately think of Date.now(). However, it only offers millisecond precision and is susceptible to system clock adjustments. As web applications demand higher performance, microsecond (μs) measurement has become essential for profiling, game development, and fine-grained animations. This article takes you deep into JavaScript's high-precision time API — performance.now(). From underlying principles to security limitations, and from code examples to microsecond concepts, we will thoroughly demystify the world of time in the browser.

1. Limitations of the Traditional Timer: Date.now()

Date.now() returns the number of milliseconds since January 1, 1970, 00:00:00 UTC. While simple to use, it has two fatal flaws:

let start = Date.now();          // e.g., 1678901234567
// Some operation...
let end = Date.now();            // Could be less than start if the clock goes back

2. Enter the High-Precision API: performance.now()

performance.now(), part of the Performance interface, returns a high-resolution floating-point number. While the unit is still milliseconds, the decimal portion can represent microseconds or even nanoseconds. Its core advantages are:

  • Monotonically Increasing: Unaffected by system clock adjustments; it always increases (unless the number range is exceeded, which is practically impossible).
  • High Resolution: Typically accurate to within 5 microseconds (depending on hardware and browser implementation).
  • Page-Load Anchored: Relative to performance.timeOrigin, making it ideal for measuring events within the same origin.

Basic Usage:

let t0 = performance.now();
// ... Code block to measure ...
let t1 = performance.now();
console.log(`Execution time: ${t1 - t0} ms`);  // Might output 0.123456789 ms

3. From Milliseconds to Microseconds: Understanding Time Units

To grasp the "high precision" of performance.now(), let's review the time scales:

UnitSymbolConversion to SecondsCommon Scenarios
Seconds1 s = 1,000 msDaily timing
Millisecondms1 ms = 10⁻³ sDate.now() precision, network latency
Microsecondμs1 μs = 10⁻⁶ s = 0.001 msAudio sampling intervals, hardware response
Nanosecondns1 ns = 10⁻⁹ s = 0.001 μsCPU instruction cycles

The integer part of performance.now() represents milliseconds, while the decimal part represents microseconds (e.g., 12.345678 ms = 12 ms and 345.678 μs). Although the specification doesn't mandate a specific precision, modern browsers typically provide at least 5 microseconds (0.005ms) resolution.

4. Practical Comparison: Date.now() vs. performance.now()

Let's demonstrate the precision difference using a very short empty loop:

// Measure an empty loop 1000 times
let d1 = Date.now();
for (let i = 0; i < 1000; i++) {}
let d2 = Date.now();
console.log(`Time via Date.now: ${d2 - d1} ms`);   // Likely outputs 0 or 1 ms

let p1 = performance.now();
for (let i = 0; i < 1000; i++) {}
let p2 = performance.now();
console.log(`Time via performance.now: ${p2 - p1} ms`); // e.g., 0.0899999999 ms

In Chrome, Date.now() almost always returns 0 (indicating <1ms), whereas performance.now() provides a value like 0.089 ms, truly capturing the tiny execution time.

5. Underlying Principles of performance.now()

Internally, browsers typically base performance.now() on one of the following low-level timers:

These underlying APIs provide microsecond or even nanosecond resolution and guarantee monotonicity.

6. Typical Use Cases: From Profiling to Game Loops

Precision Profiling

Function Execution Time

Measure the exact duration of algorithms or DOM operations to identify bottlenecks. Ideal for automated performance regression testing.

Game/Animation Loop

Delta Time Calculation

Use high-precision time within requestAnimationFrame to calculate the interval between frames, ensuring smooth physics simulations regardless of frame rate fluctuations.

Benchmarking Libraries

Benchmark.js, etc.

All serious JavaScript benchmarking libraries rely on performance.now() for microsecond-level sampling.

Audio/Real-time Processing

Precise Scheduling

The Web Audio API relies internally on high-precision time to ensure audio buffers are filled on schedule.

6.1 Delta Time in Animations

let lastTime = performance.now();
function tick() {
  let now = performance.now();
  let delta = now - lastTime;          // Interval between frames (ms), used for distance calculation
  lastTime = now;
  // Update animation position
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);

7. Security Restrictions: Why is Precision Sometimes Reduced?

Following the exposure of the Spectre vulnerability in 2018, browser vendors actively reduced the resolution of high-precision timers to prevent side-channel attacks. By default, the precision of performance.now() may be limited to 100 microseconds or even 1 millisecond, depending on the browser and platform.

Current Mainstream Restrictions:

  • Chrome: Default precision is 100 microseconds (0.1ms), with added random jitter to further mitigate timing attacks.
  • Firefox: Default precision is 1ms, sometimes 0.5ms.
  • Safari: Default precision is 1ms.

If your application genuinely requires higher raw precision (e.g., internal performance tools), you can restore it via Cross-Origin Isolation. Set the following two HTTP headers:

Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin

Once enabled, the browser lifts precision restrictions on APIs like performance.now(), returning hardware-level microsecond time.

8. Compatibility and Fallbacks

In very old browsers (e.g., IE9), performance.now() may not exist. In such cases, you can use a fallback based on Date.now(), though you will lose high precision and monotonicity:

var perfNow = (function() {
  if (window.performance && window.performance.now) {
    return function() { return performance.now(); };
  } else {
    var start = Date.now();
    return function() { return Date.now() - start; };  // Loses global monotonicity but retains relativity
  }
})();

9. Advanced: User Timing API & performance.mark

Beyond simple now() calls, the Performance API provides mark() and measure() for creating readable performance timelines:

performance.mark('start');
// Execute some task
performance.mark('end');
performance.measure('Task Duration', 'start', 'end');
let entries = performance.getEntriesByName('Task Duration');
console.log(entries[0].duration);  // Microsecond-precise duration

This data can be visualized in the Performance panel of Chrome DevTools.

10. The Precision Ceiling: Smallest Representable Value?

performance.now() returns a double-precision floating-point number (64-bit). IEEE 754 can precisely represent integers up to 2^53 (approx. 900 billion days). For relative time (differences usually within a few seconds), the decimal precision remains at the microsecond level (10⁻⁶) or better, fully satisfying daily measurement needs.

11. Real Measurement Examples: Microsecond Differences

The following code shows the tiny duration of different operations:

let t = performance.now();
let a = 1 + 2;
let t2 = performance.now();
console.log(`Addition operation time: ${(t2 - t) * 1000} μs`);  // Might be 0.5 μs

Note: Single extremely short operations may be noisy; it's common to run loops and take an average.

12. Considerations and Pitfalls

Conclusion

From the rough milliseconds of Date.now() to the microsecond granularity of performance.now(), JavaScript developers can finally capture every pulse of their code with precision. Whether building high-performance animations, conducting granular performance profiling, or developing time-sensitive audio applications, performance.now() is an indispensable tool. Understanding its underlying principles, precision limits, and security strategies empowers you to develop complex web applications with greater confidence.

Remember: When you need to measure extremely short durations or require monotonic increasing time, embrace performance.now(). It opens the door to the microsecond world.