Next up

Is your mouse
any good?

Four free tests for the mouse on your desk. Polling rate, buttons, double-clicks and click speed. Everything runs right here in your browser.

100% free No ads No tracking No sign-up No judgment
Got a Mac? Every polling test on a Mac says 120, ours too. That's your screen, not your mouse. See your real number.
Move your mouse. Look behind it.

See those dots? That's your mouse talking.

Every dot is one report your mouse sends to your computer, saying where it is. More dots per second means the cursor stays closer to your hand.

0
Move your mouse

Pick a test. Move, click, scroll.

Each test takes under a minute. Your results stay on this page and disappear when you close it.

This test needs a mouse.

You're on a phone or a tablet, so there's no mouse here to test. Send yourself the link, then open it on the computer your mouse is plugged into. It takes about 2 minutes.

Email it to myself
Runs in your browser

Your mouse's polling rate is how many times per second it tells your computer where it is. Move your mouse in circles inside the box and keep going for about 5 seconds.

0Hz
Waiting for your mouse
Move your mouse in here, in circles

Each orange dot is one report from your mouse. The closer together the dots, the faster your mouse.

Same hand. Different mouse.

Polling rate is the number of position updates per second. More updates means the cursor sits closer to where your hand actually is.

One dot per update, 10x slower

The same movement at 4 polling rates, and yours at the bottom. Watch how the gaps close up.

125Hzoffice, Bluetooth
500Hzolder gaming
1000Hzgaming standard
4000Hzhigh end
Youyour mouse
Move your mouse at the top of the page, or run test 1.
125Hz = an update every 8 ms. 1000Hz = every 1 ms.
On Windows, use Chrome or Edge. The browser sees every update, so the number is your mouse's real rate.
On a Mac, every browser stops at your screen's refresh rate. The free Mac app shows the real number.
Bluetooth almost always runs slower than the receiver that came with your mouse. Test both if you have them.
A browser sees the updates that reach the page, so this is close, not lab exact. Run it 2 or 3 times.

Nothing leaves this page

No account. No cookies. No analytics. No ads. The test runs on your computer, and your results vanish when you close the tab.

Testing on a Mac?

Got a Mac?

On a Mac, every mouse test says 120. That's your screen, not your mouse. Our free Mac app shows the real number.

  1. Download Mouse Rate. The full four-step test as a free Mac app, with your mouse's real polling rate.
    Free and open source · macOS 13 or newer · Apple silicon and Intel
  2. Drag it into Applications, then open it. The first time, macOS says it can't check the app. That's only because it isn't signed with a paid Apple certificate yet. Open System Settings, Privacy & Security and click Open Anyway.
  3. Allow Input Monitoring, then move your mouse. Your real number shows up right away.
Prefer Terminal? Use the script instead

Open Terminal (Cmd Space, type Terminal), paste with Cmd V and press Return. Move your mouse. Press Control C to stop.

If your Mac asks about Input Monitoring, allow Terminal, then type swift ~/hidrate.swift and press Return. If it says swift isn't found, run xcode-select --install once.

Terminal
~ % swift ~/hidrate.swift
Watching: Keychron Link (USB)
Watching: MX Master 3 (Bluetooth Low Energy)
Keychron Link (USB): 974 reports/s
MX Master 3 (Bluetooth Low Energy): 129 reports/s
Read the script
// hidrate.swift  (version 3)
// Counts the raw HID reports per second that each mouse sends to this Mac.
// This is what the mouse actually does, below the browser and below macOS event coalescing.
// Version 3 shows one line per physical mouse. Some receivers appear to macOS twice; the duplicate is skipped.
//
// Run it:   swift hidrate.swift
// Stop it:  Control+C
// The first run asks for Input Monitoring permission for Terminal
// (System Settings > Privacy & Security > Input Monitoring). Allow it, then run again.

import Foundation
import IOKit.hid

var counts: [String: Int] = [:]          // key: device label
var seen = Set<String>()
var labels: [UnsafeMutableRawPointer: String] = [:]

let reportCallback: IOHIDReportCallback = { _, _, sender, _, _, _, length in
    guard let sender = sender, length > 0 else { return }
    let label = labels[sender] ?? "unknown device"
    counts[label, default: 0] += 1
}

let deviceMatched: IOHIDDeviceCallback = { _, _, _, device in
    let key = Unmanaged.passUnretained(device).toOpaque()
    if labels[key] != nil { return }
    let product = (IOHIDDeviceGetProperty(device, kIOHIDProductKey as CFString) as? String) ?? "mouse"
    let transport = (IOHIDDeviceGetProperty(device, kIOHIDTransportKey as CFString) as? String) ?? "?"
    let page = (IOHIDDeviceGetProperty(device, kIOHIDPrimaryUsagePageKey as CFString) as? Int) ?? 0
    let usage = (IOHIDDeviceGetProperty(device, kIOHIDPrimaryUsageKey as CFString) as? Int) ?? 0
    let location = (IOHIDDeviceGetProperty(device, kIOHIDLocationIDKey as CFString) as? Int) ?? 0
    let dedupe = "\(product)|\(transport)|\(location)|\(page)|\(usage)"
    if seen.contains(dedupe) { return }
    seen.insert(dedupe)
    let label = "\(product.trimmingCharacters(in: .whitespaces)) (\(transport))"
    labels[key] = label
    let reported = (IOHIDDeviceGetProperty(device, kIOHIDMaxInputReportSizeKey as CFString) as? Int) ?? 64
    let size = max(reported, 8)
    let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
    IOHIDDeviceRegisterInputReportCallback(device, buffer, size, reportCallback, nil)
    IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue)
    print("Watching: \(label)")
}

let manager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))
let matching: [[String: Any]] = [
    [kIOHIDDeviceUsagePageKey: kHIDPage_GenericDesktop, kIOHIDDeviceUsageKey: kHIDUsage_GD_Mouse],
    [kIOHIDDeviceUsagePageKey: kHIDPage_GenericDesktop, kIOHIDDeviceUsageKey: kHIDUsage_GD_Pointer],
]
IOHIDManagerSetDeviceMatchingMultiple(manager, matching as CFArray)
IOHIDManagerRegisterDeviceMatchingCallback(manager, deviceMatched, nil)
IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue)

let result = IOHIDManagerOpen(manager, IOOptionBits(kIOHIDOptionsTypeNone))
if result != kIOReturnSuccess {
    print("Could not open the HID manager (code \(result)).")
    print("Give Terminal permission under System Settings > Privacy & Security > Input Monitoring, then run this again.")
    exit(1)
}

print("Move the mouse in circles, steadily, without stopping. One line per second. Control+C to stop.")
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
    if counts.isEmpty { print("(no movement)"); return }
    let line = counts.map { "\($0.key): \($0.value) reports/s" }.sorted().joined(separator: "    ")
    counts.removeAll()
    print(line)
}
RunLoop.main.run()
Browser said120
vs
Mouse Rate said974

Same mouse, same Mac.

  • No network code. It can't send anything anywhere, and every build checks for that automatically.
  • It only counts. It never reads your keys, your clicks or where your cursor is. It just counts how many reports your mouse sends.
  • Open source. Every line of the app is public on GitHub. Read it, or build it yourself.
  • Built in public. Each download is built by GitHub from the published code, with a checksum you can compare.