Summarize click intervals in JavaScript without diagnosing the mouse
Two click events close together are an observation. They are not, on their own, proof that a mouse switch is faulty. A useful diagnostic interface should preserve that distinction in its data model and its labels.
This small JavaScript exercise turns an ordered list of timestamps into an interval summary. It runs without browser APIs in OneCompiler's JavaScript runner or Node.js, so the arithmetic can be checked independently of event collection.
Define the input before calculating
The input is a list of nonnegative, finite millisecond timestamps from one clock, in the order they were observed. Do not mix Date.now() with performance.now(), combine separate browsing sessions, or silently sort the timestamps. Sorting would hide an ordering mistake rather than repair its cause.
A list of five clicks has four intervals. The first click establishes the starting timestamp; it has no preceding interval. Empty and single-click lists therefore return medianMs: null, rather than pretending that the typical gap is zero.
Equal timestamps remain valid. Browser timer precision can be reduced, so two recorded timestamps may be equal. Keep the resulting zero interval visible without calling it an infinitely fast physical action.
A pure function
// Standalone educational example. No DOM, network, or dependencies.
function summarizeClicks(times, fastBelowMs = 300) {
if (!Array.isArray(times) || !Number.isFinite(fastBelowMs) || fastBelowMs <= 0) {
throw new TypeError('Use a timestamp array and a positive finite threshold.');
}
for (let i = 0; i < times.length; i++) {
if (!Number.isFinite(times[i]) || times[i] < 0 ||
(i > 0 && times[i] < times[i - 1])) {
throw new TypeError('Timestamps must be finite, nonnegative and nondecreasing.');
}
}
const gaps = times.slice(1).map((time, i) => time - times[i]);
const sorted = [...gaps].sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
const medianMs = !sorted.length ? null : sorted.length % 2
? sorted[middle]
: sorted[middle - 1] + (sorted[middle] - sorted[middle - 1]) / 2;
return {
clicks: times.length,
intervals: gaps.length,
medianMs,
fastIntervals: gaps.filter(gap => gap < fastBelowMs).length,
fastBelowMs,
gaps
};
}
// Synthetic input, not measurements from a physical mouse.
console.log(summarizeClicks([0, 600, 1200, 1240, 1840]));
if (typeof module !== 'undefined') module.exports = { summarizeClicks };
The function creates a sorted copy of the intervals only to calculate their median. It preserves the original chronological gaps array and does not modify the input. This matters when reviewing the unusual event in context.
The example produces four gaps: 600, 600, 40, 600. Its median is 600 ms, yet one gap is below the illustrative 300 ms threshold. A normal-looking median can hide a short outlier, which is why both the chronological gaps and the fast-interval count are returned.
The threshold is a description of this calculation, not a diagnosis or a hardware specification. The strict comparison means exactly 300 ms is not flagged when fastBelowMs is 300. Return the threshold alongside the count so a later reader knows what was counted. Choose a threshold deliberately for the observation protocol; do not claim a universal switch-bounce boundary.
Check the edge cases
Try these inputs before wiring an event listener:
| Input | Expected result |
|---|---|
[] | 0 intervals; median null |
[0] | 0 intervals; median null |
[0, 300] | median 300; 0 fast intervals at threshold 300 |
[0, 0] | median 0; 1 fast interval; no physical diagnosis |
[0, 100, 400] | gaps 100/300; median 200; 1 fast interval |
[100, 90] | Reject decreasing timestamps |
[0, NaN] | Reject non-finite timestamps |
These are synthetic arithmetic checks. They say nothing about a particular mouse, switch, USB connection or person's click speed.
Collect events without double-counting
When adding a browser interface, choose one event stream and explain it. Do not append both click and dblclick timestamps: the browser dispatches dblclick after two click events, so that would count the same interaction again. A click event can also come from keyboard activation or assistive technology; it is not automatically a physical mouse press.
For a mouse-only experiment, a dedicated target and explicit primary-mouse filtering using pointer events are possible, but remember that browser events are already downstream of device firmware, operating-system processing and browser scheduling. An event log does not expose raw electrical contact bounce, and software-generated events must not be represented as physical measurements.
Reset between trials, retain the procedure with the data, and compare deliberate slow single presses with intentional double-clicks. If unexpectedly close events recur during slow single presses, repeat the same procedure with a second mouse or another computer before attributing the cause. Do not replace raw evidence with a green/red verdict.
For a ready-made interface to compare against this exercise, the KeyboardTester.click click-interval detector displays click intervals for manual checks. I operate KeyboardTester.click; this is a disclosed practical reference, and the exercise above is useful independently of that website. AI assisted preparation of this tutorial; the included arithmetic example was reviewed and tested with the stated synthetic cases.
References
The core design choice is simple: report what the browser observed, keep the test conditions, and leave physical-cause attribution to further evidence.