---
title: Task Dependencies
description: Turborepo helps you specify task dependencies declaratively.
---

import { Callout } from "../../../../../components/Callout";
import HeartIcon from "@heroicons/react/solid/HeartIcon";
import { Tabs, Tab } from '../../../../../components/Tabs'

# Task Dependencies

Turborepo is most powerful when you express how your tasks relate to each other.
We refer to these relationships as "dependencies", but they are not the same as
package dependencies that you install from your `package.json` files. While Turborepo
does understand your workspaces, it does not automatically draw any
relationships between their tasks, unless you express them in `turbo.json` via
the `dependsOn` configuration.

Let's walk through some common patterns on how to make a task depend on other tasks.

## From the same workspace

There might be tasks that need to run _before_ other tasks. For instance,
`build` might need to be run before `deploy`.

If both tasks are in the same workspace, you can specify the relationship like
this:

```jsonc filename="turbo.json"
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {},
    "deploy": {
      // A workspace's `deploy` task depends on the `build` task of the same workspace.
      "dependsOn": ["build"]
    }
  }
}
```

This means that whenever `turbo deploy` is run, `build` will also be run
inside the same workspace.

## From dependent workspaces

A common pattern in monorepos is to declare that a workspace's `build` task
should only run once the `build` tasks of all _the workspaces it depends on_ are
complete.

<Callout type="info">
This can be confusing as it refers to both _workspace_ dependencies
and _task_ dependencies, which are different concepts. Workspace dependencies are `dependencies`
and `devDependencies` in `package.json`, whereas, task dependencies are `dependsOn` key in `turbo.json`.
</Callout>

The `^` symbol (called a "caret") explicitly declares that the task depends on the task in a
workspace it depends on.

```jsonc filename="turbo.json"
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      // A workspace's `build` command depends on its dependencies'
      // and devDependencies' `build` commands being completed first
      "dependsOn": ["^build"],
    }
  }
}
```

With the configuration above, if an app installs a package from another workspace, the package's
`build` script will always run before the app's `build` script.

## From arbitrary workspaces

Sometimes, you may want a workspace-task to depend on another
workspace-task. This can be especially helpful for repos migrating from `lerna`
or `rush`, where tasks are run in separate phases by default. Sometimes these
configurations make assumptions that cannot be expressed in a simple `pipeline`
configuration, as seen above. Or you may just want to express sequences of tasks
between applications or microservices when using `turbo` in CI/CD.

For these cases, you can express these relationships in your `pipeline`
configuration using the `<workspace>#<task>` syntax. The example below describes
the `deploy` script of a `frontend` application that depends on the `deploy` and
`health-check` scripts of `backend`, as well as the `test` script of a `ui`
workspace:

```jsonc filename="turbo.json"
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    // Explicit workspace-task to workspace-task dependency
    "frontend#deploy": {
      "dependsOn": ["ui#test", "backend#deploy", "backend#health-check"]
    }
  }
}
```

This explicit configuration for `frontend#deploy` may seem to conflict with the
`test` and `deploy` task configurations, but it does not. Since `test` and
`deploy` do not have dependencies on other workspaces (e.g. `^<task>`), they can
execute any time after their workspace's `build` and `test` scripts have
finished.

<Callout>
  Notes:

1. Although this `<workspace>#<task>` syntax is a useful escape hatch, we
   generally recommend using it for deployment orchestration tasks such as
   health checks, rather than build-time dependencies, so that Turborepo can
   optimize these tasks more efficiently
1. Workspace-tasks do not inherit cache configuration. You must redeclare
   [`outputs`](/repo/docs/reference/configuration#outputs) at the moment.
1. `<workspace>` must match the `name` key in the workspace's `package.json` or
   the task will be ignored.

</Callout>

## No dependencies

An empty dependency list (`dependsOn` is either undefined or `[]`) means that
nothing needs to run before this task! After all, it has no dependencies.

```jsonc filename="turbo.json"
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    // A workspace's `lint` command has no dependencies and can be run any time.
    "lint": {}
  }
}
```

## Dependencies outside of a task

Let's say you have a common `ui` package that you are using in two apps, `docs` and `web`.

```bash
apps/
  docs/package.json # Depends on ui
  web/package.json  # Depends on ui
packages/
  ui/package.json   # No workspace dependencies
turbo.json
package.json
```

You've written some TypeScript in your workspaces and it's time run `tsc` to check your types.
There are two requirements here:

- **All type checks run in parallel to keep things speedy**: Because the results of your type checks don't depend on each other, you can run all of them in parallel.
- **A change in a dependency should result in a cache miss**: If the `ui` package changes, the type checking task in `docs` or `web` should know to miss cache.

To accomplish this, you'll create a fake, recursive task in your graph and depend on it:

```jsonc filename="turbo.json"
{
  "$schema": "https://turbo.build/schema.json",
   "pipeline": {
     "topo": {
       "dependsOn": ["^topo"]
     },
     "typecheck": {
       "dependsOn": ["topo"]
     }
   }
 }
```

Since the `topo` task doesn't exist in your scripts, Turborepo will complete the task "instantly" and then look to any workspaces that were depending on that workspace. Because of this, your tasks will execute in parallel while still understanding their relationship to other workspaces in the your task graph.

  <Callout type="idea">
    The name `topo` here is not a special name. It is short for "topological", so it helps indicate
    why it exists, but you can call this task anything you want.
  </Callout>

### Why does this work?

We can more deeply understand why this works by taking a look at pipelines that _almost_ fulfill our requirements.

You can achieve parallelism with your tasks by omitting `dependsOn` from your task definition like below:

```jsonc filename="turbo.json"
{
  "$schema": "https://turbo.build/schema.json",
    "pipeline": {
      "typecheck": {} // Uh oh, not quite!
    }
}
```

Your `typecheck` tasks will successfully run in parallel - but they won't know about their workspace dependencies!

We can demonstrate this using these steps:

1. Run `turbo typecheck`
2. Change some source code in your `ui` package
3. Run `turbo typecheck --filter=web`

If you do this, you will hit cache in Step 3 - but you shouldn't! You could have created a type error in your `web` workspace that comes from the changes in the `ui` package's code. The cache hit in Step 3 would be incorrect, hiding the type error from you.

To solve this problem, you may choose to depend on your topological dependency graph directly, much like you would for a `build` task:

```jsonc filename="turbo.json"
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "typecheck": {
      "dependsOn": ["^typecheck"] // Uh oh, not quite!
    }
  }
}
```

Now you have the right cache behavior: `web` will miss cache when `ui` code changes. That's great - but we just lost the parallelism that was making our pipeline execute so fast. The `typecheck` task in the `ui` workspace now has to **finish** before the task in `web` **starts**.

What if we could depend on a task in `ui` that "finishes instantly," starting the `typecheck` commands in dependent workspaces much sooner?

This is where the "fake" `topo` task comes in:

```jsonc filename="turbo.json"
{
   "$schema": "https://turbo.build/schema.json",
   "pipeline": {
     "topo": {
       "dependsOn": ["^topo"]
     },
     "typecheck": {
       "dependsOn": ["topo"]
     }
   }
 }
```

In this pipeline, we declare a "synthetic" task called `topo`. Since we don't
have a `topo` script in any of our `package.json` files, the `turbo typecheck`
pipeline will go straight to running all `typecheck` scripts in parallel, meeting our first requirement.

But this `topo` task also creates a "synthetic" workspace-task dependency from `web` to `ui`,
and from `docs` to `ui`. This means that when you change code in `ui`, you will also get
a cache miss for the workspaces in `web` and `docs`, meeting the second requirement.

The pipeline declares that `typecheck` depends on the `topo` task, and `topo` depends on `^topo`. In
English, this means that the `topo` task of the _same_ workspace must run before all `typecheck` tasks,
and the `topo` task of all _package dependencies_ must run before the `topo` task itself.

![](/images/docs/task-graph-with-placeholder-task.png)

Why doesn't `typecheck` directly depend on `^topo`, you ask? Because we want our workspaces to _recursively_
wire up package dependencies via synthetic tasks. If `typecheck` depends on `^topo`, `turbo` will stop
adding to the graph after the first level of dependencies.
