Why your keyboard’s firmware can change your WPM
Typing tests typically treat a keystroke as an instantaneous, unambiguous event. Firmware engineers know it’s anything but. Between switch bounce, matrix scan timing, and host polling, “when” a press becomes real can move by several milliseconds. Over hundreds of presses, that small shift can inflate or deflate your words per minute (WPM) and even nudge accuracy. For a 100 WPM typist (roughly 120 ms per character), shaving 3–5 ms off each press is already 2–4% of the per‑key budget.
This piece audits how common firmware defaults—especially in QMK and ZMK—affect measured speed/accuracy, then proposes a practical way to normalize results across boards and settings.
Debounce 101 (and why “eager” changes everything)
Mechanical switch contacts don’t settle cleanly; they chatter for a few milliseconds. Debouncing filters that chatter so one physical press becomes one logical event. QMK ships a Debounce API with several algorithms and a default debounce time of 5 ms; if a keyboard doesn’t pick a specific algorithm, QMK uses a global, defer‑style method (`sym_defer_g`). In defer algorithms, the firmware waits until the input is stable for the debounce window before reporting the change. That’s robust, but it adds up to the full debounce time to your first report on press. (docs.qmk.fm)
QMK also offers “eager” variants (per‑key or per‑row). Eager on press reports immediately, then ignores further input for the debounce window. It reduces perceived latency but is less resistant to noise. The choice (eager vs. defer, global vs. per‑key) is explicit in the QMK docs and materially changes timing. (docs.qmk.fm)
ZMK’s default is also 5 ms for both press and release, and it exposes an explicit “eager‑like” configuration by setting press debounce to 0 ms and keeping release at a nonzero value—functionally similar to QMK’s asymmetric eager‑on‑press, defer‑on‑release approach. ZMK documents that eager behavior “eliminates latency” on press but is “not noise‑resistant,” and it also calls out that its debounce scan period defaults to 1 ms—important because timers are rounded up to the next scan tick. (zmk.dev)
Bottom line: with defaults alone, a QMK board that stays defer‑on‑press can wait ~5 ms before a website even receives a keydown, whereas an eager‑press configuration can report immediately.
Scan rate vs. USB/Bluetooth polling (and why transport still matters)
Two independent clocks gate when keystrokes reach your typing test:
- The keyboard’s matrix scan: firmware cycles columns/rows to see what changed. QMK explains the process in its “How a keyboard matrix works” guide. Faster scans sample more often and leave less time between a physical actuation and a detected change. (docs.qmk.fm)
- The host polling/reporting cadence: how often the device can put a HID report on the wire. Since February 26, 2022, QMK’s default USB polling interval is 1 kHz (1 ms), aligning supported platforms on a 1 ms frame. Many vendor boards do the same over USB or 2.4 GHz receivers. Bluetooth, however, is often lower; for instance, one mainstream board advertises 1000 Hz in wired/2.4G but ~90 Hz over Bluetooth. (docs.qmk.fm)
Academic measurements show clearly how polling frames shape latency distributions; forcing 1000 Hz shifts keyboard latency down versus 125 Hz (8 ms frames). That frame time stacks on top of whatever delay your firmware introduces through scanning and debouncing. (epub.uni-regensburg.de)
How much can this skew your WPM and accuracy?
- Timing shift: If your firmware uses defer‑on‑press with a 5 ms window, the test might “see” each press up to ~5 ms later than an eager‑press setup—up to ~4% of a 120 ms inter‑key interval at 100 WPM. Over a 60‑second run, that can translate into several WPM. (docs.qmk.fm)
- Transport effect: Moving from Bluetooth (~90 Hz) to USB/2.4G (often 1000 Hz) removes up to ~9 ms of reporting overhead per event (frame differences), again biasing scores even if your technique doesn’t change. (manuals.plus)
- Noise and accuracy: Eager algorithms report immediately and then suppress subsequent chatter for the debounce window; they’re faster, but the docs warn they are not noise‑resistant. In rare edge cases (electrical noise, switch issues), this can surface as stray characters that a defer algorithm would have filtered. (docs.qmk.fm)
What common defaults actually are (QMK/ZMK quick audit)
- QMK
- Default debounce time: 5 ms. (docs.qmk.fm)
- Default algorithm when unspecified: `sym_defer_g` (global, defer). (docs.qmk.fm)
- Default USB polling (since 2022‑02‑26): 1 kHz. (docs.qmk.fm)
- ZMK
- Default press/release debounce: 5 ms each; debounce scan period: 1 ms. Timers round up to the scan period. (zmk.dev)
- Eager‑like behavior: set `CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS=0` (press) and keep release > 0. (zmk.dev)
These aren’t small implementation details—they directly influence when the very first “keydown” reaches your browser.
A proposal: a fairness harness + disclosure standard for typing sites
Here’s a pragmatic two‑part plan typing sites can adopt without forcing users to flash firmware.
1) Pre‑test “conditions check” (software‑only)
- Ask users to confirm connection type (USB/2.4G/Bluetooth) and firmware family (QMK/ZMK/vendor). Offer brief guides to find these.
- Run a 10‑second calibration where the site records high‑resolution keydown timestamps while the user alternates between two keys at a steady pace. From the inter‑event distribution, estimate whether events cluster on 1 ms vs. 8–11 ms boundaries (a strong hint of ~1000 Hz vs. ~90–125 Hz reporting). This won’t reveal the debounce algorithm, but it tags the transport reasonably well (validated by the literature’s polling‑frame effects). (epub.uni-regensburg.de)
2) Optional “verified” mode (helper utility)
- Provide a tiny open‑source helper (native or WebHID where available) that reads the keyboard’s interrupt endpoint and logs: effective report interval, jitter, and whether per‑press timings line up with eager‑like behavior. For privacy, hash and display only aggregate stats during the session. (HID spec and OS APIs allow reading polling intervals from descriptors or via driver IOCTLs on supported platforms.) (usb.org)
3) Display a Conditions Capsule with every score
- Firmware family/version (QMK 0.x commit / ZMK v0.3 or vendor)
- Debounce algorithm and window (e.g., `asym_eager_defer_pk`, 5 ms)
- Debounce scan period if exposed (e.g., 1 ms on ZMK)
- Connection and effective polling rate (e.g., USB 1000 Hz, BT ~90 Hz)
- Verification status (self‑reported vs. helper‑verified)
Even without perfect introspection, this standard makes comparisons honest. Two 120 WPM scores mean different things if one was on Bluetooth with defer‑on‑press and the other on eager‑press USB.
Actionable tips for typists (aiming for apples‑to‑apples)
- Prefer wired or 2.4 GHz over Bluetooth for competitive tests; many boards run 1000 Hz on USB/2.4G but ~90 Hz on BT. Your results will be more comparable across systems. (manuals.plus)
- On QMK: if you want snappier feel, set an eager‑on‑press algorithm such as `asym_eager_defer_pk` and keep `DEBOUNCE` modest (3–5 ms). If you care about strict fairness across boards, stick to the default `sym_defer_g` at 5 ms and disclose it. (docs.qmk.fm)
- On ZMK: for eager‑like press, set `CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS=0` and `CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS=5`. For a slightly safer profile, set press to 1 ms instead of 0. (zmk.dev)
- Keep fancy lighting modest. Heavier effects can cost scan cycles or CPU time on some boards, potentially shifting effective timing. (QMK explains how scanning dominates firmware cycles.) (docs.qmk.fm)
- If your site of choice supports it, run the “verified” pre‑test and attach the Conditions Capsule to your leaderboard runs.
What we’ll adopt on our site
- A one‑time conditions check before ranked runs, classifying your transport and estimating report cadence.
- Optional helper for “verified” sessions that reports effective polling and timing jitter.
- A visible Conditions Capsule on every public scorecard so cross‑board comparisons stay fair.
We’re not forcing anyone to flash firmware—just surfacing the hidden timing knobs that quietly move your WPM.
The takeaway
Debounce algorithms, scan periods, and host polling are invisible to most users, yet they change when each letter lands. QMK/ZMK defaults cluster around 5 ms windows; eager vs. defer flips whether you pay that cost up front on press; and transport can tack on anything from ~1 ms (USB 1 kHz) to ~10 ms (BT/125 Hz). Put those together, and you have a recipe for score drift. A lightweight harness and clear disclosure closes the loop—so your WPM reflects fingers, not firmware. (docs.qmk.fm)