The Task Graph
The previous section talked about how Turborepo uses
express how tasks relate to each other. You can think of these relationships as
dependencies between tasks, but we have a more formal name for them: the Task
Turborepo uses a data structure called a directed acyclic graph (DAG) (opens in a new tab) to understand your repository and its tasks. A graph is made up of "nodes" and "edges". In our Task Graph, the nodes are tasks and the edges are the the dependencies between tasks. A directed graph indicates that the edges connecting each node have a direction, so if Task A points to Task B, we can say that Task A depends on Task B. The direction of the edge depends on which task depends on which.
For example, let's say you have a monorepo with an application
depends on two packages:
build task that depends on
Turborepo will build a task graph like this:
A challenge when building a Task Graph is handling nested dependencies. For
example, let's say your monorepo has a
docs app that depends on the
package, which depends on the
Let's assume the
docs app and the
core package each have a
build task, but
ui package does not. You also have a
turbo.json that configures the
build task the same way as above with
"dependsOn": ["^build"]. When you run
turbo run build, what would you expect to happen?
Turborepo will build this Task Graph:
You can think of this graph in a series of steps:
docsapp only depends on
uipackage does not have a build script.
uipackage's dependencies have a
buildscript, so the task graph knows to include those.
Turborepo calls the
ui package a Transit Node in this scenario, because it
doesn't have its own
build script. Since it doesn't have a
Turborepo won't execute anything for it, but it's still part of the graph for
the purpose of including its own dependencies.
What if we didn't include Transit Nodes in the graph?
In the example above, we're including the
ui node (and its dependencies) in
the Task Graph. This is an important distinction to make sure that Turborepo
misses the cache when you'd expect.
If the default was to exclude Transit Nodes, a source code change in the
core package would not invalidate the cache for the
docs app for
turbo run build, using stale code from previous iterations of your
What if the
docs/ package didn't implement the
build task? What would
you expect to happen in this case? Should the
core packages still
execute their build tasks? Should anything happen here?
Turborepo's mental model is that all nodes in the Task Graph are the same. In other words,
Transit Nodes are included in the graph regardless of where they appear in the graph.
This mental can have unexpected consequences. For example, let's say you've configured
build task to depend on
Let's say your monorepo has many apps and many packages. All packages have
test tasks, but only one app have a
build task. Turborepo's mental model
says that when you run
turbo run build, even if an app doesn't implement
test task of all packages that are dependencies will show up in the graph.