# grunt-concurrent: running groups of grunt tasks in parallel

> grunt-concurrent is a Grunt plugin that runs groups of tasks at the same time, with a concurrency limit that defaults to twice the number of CPU cores and options for interleaving the output of long running tasks. It is MIT licensed, its only release is from 2019, and the repository is still being pushed to.

**sindresorhus/grunt-concurrent** — Run grunt tasks concurrently

- Repository: https://github.com/sindresorhus/grunt-concurrent
- Stars: 788 · Forks: 67
- Language: JavaScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/sindresorhus-grunt-concurrent

## A task whose job is to run other tasks

grunt-concurrent is a Grunt plugin that runs grunt tasks concurrently, and it is installed as a development dependency:

```bash
npm install --save-dev grunt-concurrent
```

The use case it names is a build that is slow because of independent work. Running slow tasks like Coffee and Sass concurrently can potentially improve your build time significantly. The second use case is different in kind: it is also useful if you need to run multiple blocking tasks like nodemon and watch at once, which is the classic case of a development server and a file watcher competing for the terminal.

The configuration is a concurrent block whose keys are target names and whose values are lists of grunt task names:

```js
require('load-grunt-tasks')(grunt);

grunt.initConfig({
	concurrent: {
		target1: ['coffee', 'sass'],
		target2: ['jshint', 'mocha']
	}
});
```

Then you register the targets as ordinary grunt tasks. Nothing about the tasks themselves changes. They remain grunt tasks, invoked by name, and the plugin runs a set of them side by side.

That is the whole idea, and it is a small one with a wide reach. Grunt executes a task queue in order, so two compilers that do not depend on each other still wait for each other. The plugin gives you a way to say that a set of tasks may overlap, without rewriting them.

## Concurrency is per target, not across targets

The comment in the documentation's usage example is the part that catches people out, because the obvious reading of the configuration is wrong.

Tasks of target1 run concurrently, after they all finished, tasks of target2 run concurrently, instead of target1 and target2 running concurrently. And the registration line makes that explicit:

```js
// Tasks of target1 run concurrently, after they all finished, tasks of target2 run concurrently, instead of target1 and target2 running concurrently.
grunt.registerTask('default', ['concurrent:target1', 'concurrent:target2']);
```

So parallelism is scoped to a single target. Two targets registered in sequence run one after the other, and within each target the listed tasks overlap. If you want four tasks to run four at once, they all have to be in one target, and the groups of tasks that must not overlap with each other have to be in different targets.

That is a sensible design rather than a limitation to complain about, because it keeps the ordering guarantees that a build needs. A target is the unit of concurrency, and a task chain of targets is the unit of sequencing. A project that needs the Coffee output before the JavaScript concatenation still expresses that by putting the dependency in its own target, or simply by listing it in a later registered task.

The style of the configuration follows from that. The array form, target1: ['coffee', 'sass'], is for the simple case, and the object form with tasks and options, used in the options examples, is what you need when you want to pass options to a target.

## Sequential steps inside a concurrent target

Sometimes one task in a parallel group has to finish before the rest of the group starts, and the plugin supports that with a nested array:

```js
grunt.initConfig({
	concurrent: {
		target: [['jshint', 'coffee'], 'sass']
	}
});
```

Now jshint will always be done before coffee and sass runs independent of both of them.

The semantics are worth stating precisely, because they are not the same as a plain sequence. Inside the target, the first element is a nested array of two tasks that run one after the other, and the second element is a single task. The two elements then run concurrently with each other. So the group is still parallel, with one member of the group ordered internally.

The practical use is a check that gates part of the work. If jshint must pass before the compiled output is produced, but the Sass compilation is independent of both, this is the shape that expresses it. The same mechanism lets you order a pair of tasks that must not overlap, for example two tasks that write to the same file, while the rest of the group proceeds in parallel.

It is worth noting what the plugin does not do. It does not retry a failed task, it does not cancel the remaining tasks when one fails, and it does not know which tasks depend on which. The dependency knowledge stays with you, in the order you write.

## The limit option is the real control

The first option in the list is the one that decides whether the plugin helps or hurts.

The limit option is a number, and its default is twice the number of CPU cores with a minimum of 2. It limits how many tasks that are run concurrently.

A default of twice the cores is a reasonable starting point for a build where the tasks are CPU bound, because build tools tend to be partly serialised and partly parallel, so leaving some headroom keeps the machine busy. It is also a default that can be wrong on a build agent that is already running other things, or on a laptop that is also compiling something else, and the option is there for exactly that case.

The two behaviours of the plugin interact here. Without a limit, every task in a target starts at once, which on a large group means a machine with a lot of processes competing for a few cores. With the limit, the plugin starts tasks up to the cap and starts the next one as a slot frees. The default is a middle position, and a project that has measured a build being slower under parallelism has a specific number to set.

For comparison, the plugin's own dependency on async is what implements the scheduling, with arrify for argument handling, indent-string and pad-stream for the output formatting. The grunt peer dependency is 1 or newer, and the engines field asks for Node 8 or newer.

## Interleaved output, and when it is worth it

The second option exists because parallel tasks print to the same terminal, and that is a problem.

The logConcurrentOutput option is a boolean that defaults to false, and with it you can optionally log the output of your concurrent tasks. The documented example runs grunt-nodemon to launch and monitor a node server and grunt-contrib-watch to watch for asset changes, all in one terminal tab, with the tasks and options given in the object form of the configuration.

The documentation is blunt about the downside: the output will be messy when combining certain tasks, and this option is best used with tasks that don't exit, like watch and nodemon, to monitor the output of long-running concurrent tasks.

That is a narrower use case than it first appears. For a build that runs to completion, the sensible default is to leave the output off, because a build task's output is usually short and the parallel version interleaves it into something unreadable. For two long-running processes that never exit, there is no natural end to the output anyway, so interleaving is harmless and being able to see both streams in one terminal is the point.

The third option, indent, addresses the same problem from the other side. It is a boolean that defaults to true, and setting it to false skips indenting the log output of your concurrent tasks. The documentation gives the reason: it can be useful for running tasks in parallel for a stdout parser which expects no indentation, for example TeamCity tests.

## One release, an old toolchain, and a live repository

The maintenance picture of this plugin is unusual enough to spell out.

There is a single tagged release, v3.0.0, from 2019-06-23, and the last push to the repository was on 2026-09-18. The package version in the manifest is 3.0.0, and the repository is not archived. So the code is being worked on while the published version is more than seven years old, which is a combination that makes sense for a small utility whose stable API has not needed to move.

The published package is deliberately narrow. The files array contains only the tasks directory, which is where a Grunt plugin's task implementation lives, and the dev dependencies are the test harness: grunt, grunt-cli, cross-spawn, grunt-contrib-clean, grunt-contrib-watch, grunt-nodemon, grunt-simple-mocha, nodemon, path-exists, supports-color and xo. The test script is xo && grunt, so the linting and the plugin's own test suite run together.

The runtime dependencies are what a task runner needs and nothing more: async for the scheduling, arrify for argument handling, and indent-string with pad-stream for the output formatting. There is no CLI to learn, no configuration file format of its own, and no service to run. It is a Grunt task that takes a list of task names.

For a project still on Grunt, that is a comfortable shape: a single dev dependency, a configuration block, and a registered task.

## Conclusion

grunt-concurrent suits a team with an older Grunt pipeline whose build is slowed by two independent compilers, for example Coffee and Sass, and who would rather change a configuration file than restructure the build. It is the wrong choice for a project that has moved to a different task runner, since the plugin is a Grunt plugin and its peer dependency is grunt at 1 or newer, and it is the wrong choice if the tasks in question write to the same output files, because nothing in the plugin coordinates writes. Verify three things before you adopt it: that the tasks you want to run in parallel are genuinely independent, that the default concurrency limit of twice the CPU cores does not oversubscribe a machine that is already busy, and that a plain nested task array in your Gruntfile would not achieve the same thing with less machinery.

## FAQ

### What does grunt-concurrent do?

It is a Grunt plugin that runs groups of grunt tasks at the same time. The documented use case is slow independent build steps such as Coffee and Sass, and also running several blocking tasks like nodemon and watch in one terminal.

### How do I install and use grunt-concurrent?

Install it with npm install --save-dev grunt-concurrent, add a concurrent block to your Gruntfile with a target whose value is a list of task names, then register that target as an ordinary task, for example grunt.registerTask('default', ['concurrent:target1', 'concurrent:target2']).

### Do two concurrent targets run at the same time?

No. Tasks of one target run concurrently, and after they have all finished the tasks of the next target run concurrently. Parallelism is scoped to a single target, and a chain of registered targets gives you sequencing between groups.

### Can I keep one task ordered before the others in a concurrent target?

Yes, by nesting an array inside the target, for example target: [['jshint', 'coffee'], 'sass']. The nested pair runs one after the other, and the whole group still runs concurrently with the rest of the target.

### How many tasks does grunt-concurrent run at once by default?

The limit option defaults to twice the number of CPU cores with a minimum of 2, and it caps how many tasks run concurrently. Setting it lower is the first thing to try if a build machine is already busy.

### How do I see the output of concurrent tasks, and what licence is it under?

Set the logConcurrentOutput option to true, with the caveat that the output becomes messy and the option is best used with tasks that never exit, such as watch and nodemon. The indent option can be set to false when a stdout parser expects no indentation. The plugin is MIT licensed, its peer dependency is grunt 1 or newer, and its engines field asks for Node 8 or newer.

## Sources

- [Issues](https://github.com/sindresorhus/grunt-concurrent/issues)
- [License: MIT](https://github.com/sindresorhus/grunt-concurrent/blob/main/LICENSE)
- [README](https://github.com/sindresorhus/grunt-concurrent/blob/main/README.md)
- [Releases](https://github.com/sindresorhus/grunt-concurrent/releases)
- [sindresorhus/grunt-concurrent on GitHub](https://github.com/sindresorhus/grunt-concurrent)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sindresorhus-grunt-concurrent
