# hardware_concurrency

> The number of logical CPU threads the browser reports, such as 8 or 12, read 16 times so a value that flips is never reported.

Source: https://whiskerjs.com/signals/hardware-concurrency/ (last updated 2026-10-26)

**hardware_concurrency at a glance (from the library code, signalsVersion 9)**

| Field | Value |
| --- | --- |
| Component name | `hardware_concurrency` |
| In the visitorId | yes |
| structuredId group | [device (screen, memory, CPU)](https://whiskerjs.com/docs/identifiers/#structuredid) |
| Hashing | plain value; a digest only when longer than 128 characters or not printable ASCII |
| Excluded under | Brave (brave_farbling); Firefox resistFingerprinting (firefox_rfp); Tor Browser and Mullvad Browser (rfp_hardened) |
| Inner timeout | 1000 ms |
| Added in | [signalsVersion 1](https://whiskerjs.com/whats-new/) |

> **In plain words:**
>
> Browsers tell pages how many tasks the processor can run at once, so that sites can split heavy work. A typical laptop says 8 or 12, a big desktop more, a phone usually 6 or 8. WhiskerJS reads this number 16 times in a row: if the answers differ, the browser is varying it on purpose and no number is reported. Safari, which can alternate between 4 and 8, reports either as one bucket, `4_8`. Privacy browsers that fake it are skipped instead of read.

## What it reads

`navigator.hardwareConcurrency`, read 16 times in a row (since signalsVersion 7), reported as a plain number when it is a finite number and every read agrees.

## How the technique works

The browser reports the number of logical processors available to it. WhiskerJS reads the property through the page’s `navigator`:

1. If the property is missing or not a finite number the component is `unavailable` / `no_api`; if the getter throws (for example because a script replaced it with one that throws) the runner reports `blocked` / `threw` or `security_error`.
2. The value is read 16 times. If any read differs from the first, the component is `noisy` / `read_varies` and carries no value, never one of the values, so the id does not flap. Such a value is also left out of the [`iframe_mismatch`](https://whiskerjs.com/docs/integrity/#iframe_mismatch) and [`prototype_lie`](https://whiskerjs.com/docs/integrity/#prototype_lie) cross-checks.
3. In WebKit (browser classes `safari` and `safari_afp`) a 4 or an 8 is reported as the bucket `4_8`. WebKit alternates between 4 and 8 for scripts on its known-fingerprinting list, and since that can happen between page loads as well as between reads, the bucket keeps the id steady. Other values are reported exactly.

For the [previous ids](https://whiskerjs.com/docs/identifiers/#previous-ids-linking-across-a-version-bump) of the signalsVersion 6 to 7 bump, the first of the 16 reads was recorded unbucketed, the way the old collector read it.

## Why it helps

CPU thread counts differ across device classes and models, and they combine well with memory, architecture and screen values in the `device` group of the [structuredId](https://whiskerjs.com/glossary/#structured-id).

## Stability

It is fixed for a given machine and browser (with the `4_8` bucket in Safari), but it can change when a browser starts capping or rounding the value in an update, when a virtual machine is given a different number of processors, or on a different device.

## Browser support and what blocks it

- `ok` in all current browsers ([Chrome](https://whiskerjs.com/browsers/chrome-desktop/), [Edge](https://whiskerjs.com/browsers/edge/), [Safari](https://whiskerjs.com/browsers/safari-macos/), [Firefox](https://whiskerjs.com/browsers/firefox/), mobile browsers). Some browsers cap or round it; WhiskerJS reports what they give.
- [Safari](https://whiskerjs.com/browsers/safari-macos/) and [iOS browsers](https://whiskerjs.com/browsers/safari-ios/): a 4 or an 8 is reported as `4_8`.
- [Brave](https://whiskerjs.com/browsers/brave/): `excluded` / `brave_farbling`.
- [Firefox resistFingerprinting](https://whiskerjs.com/browsers/firefox-rfp-and-tor/): `excluded` / `firefox_rfp`; [Tor Browser and Mullvad Browser](https://whiskerjs.com/browsers/firefox-rfp-and-tor/): `excluded` / `rfp_hardened`.
- A browser that varies the value per read: `noisy` / `read_varies`.

Related integrity checks look at the same property: [`impossible_hardware`](https://whiskerjs.com/docs/integrity/#impossible_hardware) (not a whole number from 1 to 256), [`navigator_own_property`](https://whiskerjs.com/docs/integrity/#navigator_own_property) (defined on the navigator object itself), [`native_tampering`](https://whiskerjs.com/docs/integrity/#native_tampering) (the getter does not look native) and [`iframe_mismatch`](https://whiskerjs.com/docs/integrity/#iframe_mismatch) (a clean frame reports a different number).

## Privacy

A single number about the processor, read without permission or prompt, never sent by WhiskerJS.

## How WhiskerJS handles noise and failures

| Status | ruleId | When |
| --- | --- | --- |
| `ok` | none | for example `8`, or `4_8` in WebKit |
| `noisy` | `read_varies` | the 16 reads did not all agree |
| `unavailable` | `no_api` | missing or not a finite number |
| `blocked` | `security_error`, `threw` | the getter threw |
| `excluded` | `brave_farbling`, `firefox_rfp`, `rfp_hardened`, `caller_excluded` | exclusion table or your list |
| `timed_out` | `timeout`, `aborted` | 1000 ms inner timeout, budget, abort |

## Example value

```json
"hardware_concurrency": { "status": "ok", "value": "8", "hashed": false }
```

The container test runs used for these pages reported `14` (headless Chromium and Firefox) and `8` (headless WebKit) on the same host, which shows that browsers do not all report the same number for one machine. Since signalsVersion 7 that WebKit `8` is reported as `4_8`.

## Questions and answers

### Is hardware_concurrency the number of CPU cores?

It is the number of logical processors the browser exposes, which usually counts threads, not physical cores. Some browsers cap or round it.

### Why is it excluded in Brave, Firefox resistFingerprinting, Tor and Mullvad?

Brave randomises it per site and session, and resistFingerprinting (also in Tor Browser and Mullvad Browser) reports a fixed number. Neither describes the device, so the exclusion table skips it.

### Why does Safari report 4_8?

WebKit gives scripts on its known-fingerprinting list a core count that alternates between 4 and 8 (Safari 26, Safari Technology Preview 254). Because the alternation can also happen between page loads, a WebKit 4 or 8 is reported as the one bucket 4_8, so the id does not flap. Other Safari values, and every value in other engines, are reported exactly.

### What does noisy with read_varies mean?

WhiskerJS read the value 16 times in a row and the reads disagreed. The browser varies it per read, so no single value is reported and the component counts as missing in the id, the same in every run.

### What does impossible_hardware mean?

The integrity check of that name fires when hardwareConcurrency is a number but not a whole number from 1 to 256, which points to a value set by hand.

## Related pages

- [device_memory](https://whiskerjs.com/signals/device-memory/): Approximate memory, Chromium only.
- [cpu_architecture](https://whiskerjs.com/signals/cpu-architecture/): x86 or ARM from a NaN bit.
- [impossible_hardware flag](https://whiskerjs.com/docs/integrity/#impossible_hardware): Catches values no real device reports.
- [navigator_own_property flag](https://whiskerjs.com/docs/integrity/#navigator_own_property): Catches overrides on the navigator object.
- [Brave](https://whiskerjs.com/browsers/brave/): Where it is excluded.
