Back to monorepo orchestration Target affected workspaces Configure turbo pipelines Compare the Nx approach

Nx vs Turborepo Performance Benchmarks

You want a defensible answer to "which task runner is faster for our monorepo," and you want it backed by numbers from your own hardware rather than a vendor landing page. This page lays out a reproducible benchmark protocol — cold and warm cache, identical runners, cleared state — explains why the two tools diverge on the same workload, and shows the configuration that makes each one's hashing deterministic so the comparison is fair. If you are still deciding which to adopt rather than measuring an existing pick, start from Choosing a Monorepo Task Runner; this page assumes you want to put both on a stopwatch.

Symptoms that trigger a benchmark

Teams reach for a head-to-head measurement when CI exhibits:

Symptoms that trigger a benchmark Teams reach for a head-to-head measurement when CI exhibits: Symptoms that trigger a benchmark Teams reach for a head-to-head measurement when CI exhibits:
Symptoms that trigger a benchmark — the core idea of this section at a glance.
• cache hit ratio: 54%  (expected > 90% on an unchanged PR)
• Task graph computation took 3.8s before any task ran
• repeated full rebuilds despite no source changes in 4 of 6 packages
• wall-clock build time varies ±40% run to run on identical input

These point at task-graph or cache-invalidation behavior, not raw compiler speed — which is exactly where Nx and Turborepo differ.

Root cause analysis

The two tools answer "what changed and what must re-run" differently. Nx builds a computed project graph from explicit project configuration and filesystem analysis, then invalidates tasks based on that graph and namedInputs. Turborepo uses a lighter content-addressable file-hashing engine in its Go binary, hashing the inputs globs you declare in your Turborepo Pipeline Configuration. When cache invalidation misaligns with real workspace boundaries — over-broad inputs, untracked transitive edges — either tool does redundant work, serializes tasks that could parallelize, or over-fetches unrelated packages. Sound measurement is part of designing a resilient Monorepo Architecture & Orchestration setup, so the protocol below controls for every variable that moves the numbers.

Cold vs warm build times Bar chart comparing illustrative cold-cache and warm-cache build durations for Nx and Turborepo. cold warm scenario Turborepo cold ~ 92s Nx cold ~ 101s Turborepo warm ~ 7s Nx warm ~ 5s bar length = wall-clock seconds (illustrative)
Illustrative shape only: cold builds are close, warm builds collapse to single-digit seconds once the cache holds — your own runs are what matter.

Resolution: a reproducible benchmark protocol

Run every measurement on identical CI runners with state fully cleared between cycles. The numbers are meaningless otherwise.

Resolution: a reproducible benchmark protocol Run every measurement on identical CI runners with state fully cleared between cycles. Resolution: a reproducible benchmark protocol Run every measurement on identical CI runners with state fully cleared between cycles.
Resolution: a reproducible benchmark protocol — the core idea of this section at a glance.

1. Clear all caches

rm -rf .turbo node_modules/.cache .nx/cache

2. Measure cold-cache builds

# Nx cold: skip the cache entirely
nx run-many --target=build --all --parallel=4 --skip-nx-cache

# Turborepo cold: force execution
turbo run build --concurrency=4 --force

3. Measure warm-cache builds and inspect hits

# Re-run with no source change; expect near-total hits
nx run-many --target=build --all --parallel=4 --verbose

turbo run build --concurrency=4 --dry=json \
  | jq '.tasks[] | {id: .taskId, cache: .cache.status}'

Capture wall-clock time, peak CPU/RAM, and hit ratio for each of the four cells (Nx/Turborepo × cold/warm). Watch the verbose and dry-run output for serialized tasks or over-fetching of unrelated packages, and tighten dependency declarations to remove false-positive invalidation.

Deterministic configuration patches

Both tools need strict input/output boundaries before a comparison is fair; otherwise an unrelated config edit busts one tool's cache and not the other's.

Deterministic configuration patches Both tools need strict input/output boundaries before a comparison is fair; otherwise an unrelated config edit busts one Deterministic configuration patches Both tools need strict input/output boundaries before a comparison is fair; otherwise an unrelated config edit busts one tool's cache and not the other's.
Deterministic configuration patches — the core idea of this section at a glance.
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"],
      "inputs": ["src/**", "package.json", "tsconfig.json"]
    }
  }
}
// nx.json
{
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["{projectRoot}/dist"],
      "cache": true
    }
  },
  "namedInputs": {
    "default": ["{projectRoot}/**/*", "sharedGlobals"],
    "sharedGlobals": ["{workspaceRoot}/tsconfig.base.json"]
  }
}

Scoping namedInputs and inputs this way stops an unrelated root-config change from triggering a full workspace rebuild in either tool.

What the four numbers actually tell you

A benchmark produces four cells; each answers a different question, and reading them together is what turns raw seconds into a decision.

What the four numbers actually tell you A benchmark produces four cells; each answers a different question, and reading them together is what turns raw seconds What the four numbers actually tell you A benchmark produces four cells; each answers a different question, and reading them together is what turns raw seconds into a decision.
What the four numbers actually tell you — the core idea of this section at a glance.
Cell What it measures What a bad number means
Turborepo cold Raw orchestration + compile overhead A slow cold run points at task serialization, not caching
Nx cold Same, plus project-graph computation time A large gap vs. Turborepo cold is graph-construction cost
Turborepo warm Restore speed from cache on an unchanged graph A warm run far above single digits means misses, i.e. hash drift
Nx warm Same, via the computed graph High warm time with low hit count means over-broad namedInputs

The cold numbers are usually close because both tools ultimately shell out to the same compilers; the interesting signal is in the warm column and the hit ratio. A warm run that is not near-instant is not a speed problem — it is a correctness problem in your inputs/namedInputs, and chasing it as "make caching faster" wastes effort. Fix determinism first, then compare warm times.

A second subtlety: Nx pays a fixed graph-computation cost on every invocation, which dominates on tiny repos and disappears on large ones, while Turborepo's per-task hashing scales with the number of files. On a repo with few packages but many files per package, Turborepo's hashing can cost more than Nx's graph walk; on a repo with many small packages, the reverse holds. This is why "which is faster" has no repo-independent answer and why the protocol insists on measuring your own tree.

Eliminating measurement noise

Three sources of variance will wreck an otherwise careful benchmark. Disk cache warmth: the OS page cache makes a second cold run faster than the first, so run each cold measurement on a fresh runner or sync && echo 3 > /proc/sys/vm/drop_caches between cycles where you control the host. Background load: a CI runner sharing a host with other jobs adds jitter, so prefer dedicated runners for the comparison and report the median of at least five cycles rather than a single value. Network variability: remote-cache download time depends on the link, so isolate the orchestration comparison by benchmarking with only the local cache first, then add the remote layer as a separate measurement.

Eliminating measurement noise Three sources of variance will wreck an otherwise careful benchmark. Eliminating measurement noise Three sources of variance will wreck an otherwise careful benchmark.
Eliminating measurement noise — the core idea of this section at a glance.
# Median of five warm runs, Turborepo, local cache only
for i in $(seq 1 5); do
  /usr/bin/time -f "%e" turbo run build --concurrency=4 2>> turbo_warm.txt
done
sort -n turbo_warm.txt | awk '{a[NR]=$1} END{print "median:", a[int(NR/2)+1]}'

CLI validation

CLI validation CLI validation in production JavaScript package workflows. CLI validation CLI validation in production JavaScript package workflows.
CLI validation — the core idea of this section at a glance.
# Confirm warm runs are actually hitting cache, not silently re-running
turbo run build --dry=json | jq '[.tasks[] | select(.cache.status=="HIT")] | length'
nx run-many --target=build --all --parallel=4 --verbose | grep -c "from cache"

Prevention and CI guardrails

  • Pin package.json and the lockfile so transitive drift cannot alter content hashes mid-benchmark.
  • Set identical remote-cache TTLs (for example 14 days on feature branches) for both tools so storage policy is not a hidden variable.
  • Document hardware specs, concurrency, and the cache-clear procedure in a runbook; re-run before merging any toolchain upgrade.
  • Use --graph or --dry output to catch implicit dependency cycles that force sequential execution and skew timings.
Prevention and CI guardrails Prevention and CI guardrails in production JavaScript package workflows. Prevention and CI guardrails Prevention and CI guardrails in production JavaScript package workflows.
Prevention and CI guardrails — the core idea of this section at a glance.

What actually determines monorepo build speed

Benchmarks comparing Nx and Turborepo can mislead, because raw task-runner overhead is rarely what determines a monorepo's build speed in practice. Both tools implement the same fundamental optimizations — a task graph for ordering, content-hash caching for replay, and affected detection for scoping — and for a given workspace, the dominant factors are how much of the build is cacheable, how accurate the dependency graph is, and whether remote caching is configured. A tool's per-task scheduling overhead is a rounding error next to a cache that hits 90% of the time versus one that hits 40%.

Speed factors Cache hit rate, graph accuracy, remote caching. cache hit rate replay vs recompute graph accuracy narrow affected remote caching shared results
Real speed comes from configuration — cache hits and graph accuracy — not tool overhead.

This means the most impactful performance work is not choosing between the tools but tuning whichever you have: declaring inputs and outputs precisely so cache keys are stable and complete, keeping the dependency graph accurate so affected detection is narrow and correct, and configuring remote caching so results are shared across CI and developers. A poorly-configured pipeline on the faster tool loses to a well-configured pipeline on the slower one every time. Benchmarks that measure cold, uncached, single-machine builds capture the least representative case; the realistic question is how fast the pipeline is with warm caches, accurate affected detection, and remote sharing — which is a property of the configuration far more than of the tool.

Choosing on capabilities, not benchmark numbers

Since both tools deliver comparable performance when well-configured, the choice should be driven by capabilities and fit rather than benchmark deltas. Turborepo optimizes for a minimal, fast-to-adopt, JavaScript-first experience: a small turbo.json, sensible defaults, and quick setup, which suits teams that want caching and affected builds without a large configuration surface. Nx offers an integrated platform — a project graph, code generators, executors, module-boundary enforcement, and distributed task execution — which suits large, multi-language workspaces that benefit from the extra structure.

Capability fit Turborepo versus Nx by needs, not benchmarks. Need Turborepo Nx Config minimal platform Codegen none generators Best for JS-first multi-lang platform
Both are fast when tuned — choose on capabilities and workspace trajectory.

The decision that matters is where your workspace is headed. A JavaScript monorepo that mainly needs fast, cached, affected builds is well served by Turborepo and may never need more; a workspace growing toward dozens of projects across multiple languages, wanting enforced boundaries and codegen, is better served by Nx's platform. Both will be fast if configured well, so a benchmark showing one a few percent ahead on a synthetic workload is not a good reason to pick it over the one that fits your team's needs. Evaluate the tools on their capabilities, their configuration ergonomics, and their fit with your workspace's trajectory — and invest the effort you might have spent chasing benchmark numbers into configuring the pipeline properly, which is where the real performance lives.

How to benchmark your own workspace meaningfully

If you do want to compare the tools on your actual workspace — which is far more informative than a synthetic benchmark — measure the cases that reflect real use rather than a cold, uncached single build. The meaningful measurements are: a warm-cache build after a small change (what developers experience most), an affected build for a typical pull request (what CI runs), and a cold build only as a worst-case baseline. Measuring only the cold build captures the least representative case and the one least affected by the optimizations that matter.

Benchmark realistically Measure warm-cache and affected builds, not cold. warm-cache build after small change affected build typical PR control variables same config
Measure the cases developers and CI hit; control the config so you compare tools.

Control the variables so the comparison is fair: the same machine, the same declared inputs and outputs, the same remote-cache configuration, and the same dependency graph. Because both tools' performance is dominated by cache hit rate and graph accuracy, an unfair comparison — one tool with remote caching and precise inputs, the other without — measures the configuration, not the tool. Run each scenario several times and report the warm-cache and affected-build numbers, since those are what determine day-to-day speed. In practice, a fair comparison on a well-configured workspace usually shows the tools within noise of each other, which is why the decision should rest on capabilities and fit — and why the effort of benchmarking is often better spent configuring whichever tool you choose to hit a high cache hit rate.

Frequently Asked Questions

Which tool delivers faster cold build times for large TypeScript monorepos? They are usually close, with Turborepo's Go binary and file-level hashing giving it a small edge on cold runs; Nx can pull ahead in highly interconnected workspaces where its project-graph inference avoids re-running tasks that Turborepo's coarser hashing would repeat. Measure your own graph rather than trusting a general ranking.

How do cache invalidation strategies differ between Nx and Turborepo? Nx invalidates from a computed project graph with explicit dependency tracking; Turborepo invalidates from content-addressable hashing of declared inputs. Turborepo's model is easier to reason about for a single library, while Nx offers finer control over workspace boundaries in deeply connected repos.

What concurrency setting makes a benchmark fair? Match the runner's CPU core count — --concurrency=4 for Turborepo and --parallel=4 for Nx on a 4-core runner — and hold it constant across every cell, alongside identical package versions and cleared caches, so the only variable is the tool.

Is Nx or Turborepo faster?

For a well-configured workspace, comparably — both implement the same task-graph, caching, and affected-detection optimizations, so real-world speed is determined by cache hit rate, dependency-graph accuracy, and remote caching far more than by the tool. A poorly-configured pipeline on the faster tool loses to a well-configured one on the slower.

Should I choose a task runner based on benchmarks?

No — choose on capabilities and fit, since both are fast when configured well. Turborepo for a minimal, JavaScript-first setup; Nx for a large multi-language platform with codegen and boundary enforcement. Invest in configuring the pipeline rather than chasing benchmark deltas.

How should I benchmark Nx versus Turborepo on my workspace?

Measure warm-cache builds after a small change and affected builds for a typical PR — the cases developers and CI actually hit — not just a cold single build. Control the machine, inputs, remote-cache config, and graph so you compare the tools, not the configuration. Fair comparisons usually show them within noise.

Related

Turborepo Pipeline Configuration