GitHub just added parallel steps for GitHub Actions, and my first reaction was boring: nice, now the messy workflow files are going to get messier.
That sounds cynical, but it comes from real CI pain. Most slow pipelines I see aren't slow because one command takes forever. They're slow because teams keep stacking unrelated work inside one job until nobody wants to touch the YAML anymore.
GitHub Actions parallel steps give teams a cleaner way to run independent checks inside the same job. That matters for small teams because it can shorten feedback time without forcing a full CI rewrite.
But there's a catch. Parallel work exposes every sloppy assumption your pipeline has been hiding.
What changed in GitHub Actions parallel steps
GitHub's changelog says Actions steps can now run in parallel. Before this, steps inside a job ran in order. If you wanted parallel work, the common path was splitting checks into separate jobs or using a matrix.
That worked, but it came with overhead. Separate jobs often repeat checkout, install dependencies again, rebuild the same app, and make logs harder to scan. Matrix jobs are great for language versions or test shards, but they feel heavy when you just want lint, typecheck, and a small test command to run at the same time.
Parallel steps sit in the middle. They let teams keep one job shape while letting independent commands run together.
For example, this kind of workflow becomes easier to reason about:
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm testThe dependency install still happens once. After that, lint, typecheck, and tests may be candidates for parallel execution if they don't write to the same files.
Small thing. Big difference when developers are waiting on red or green before merging.
The boring checklist before turning it on
Don't start by parallelizing everything. Start by asking one question: which commands are truly independent?
A safe first batch usually looks like this:
- lint that reads source files
- typecheck that writes no shared output
- unit tests that don't depend on a running database
- formatting checks that run in check mode
A risky batch looks different:
- commands that write coverage files to the same path
- tests that create shared temp files
- scripts that mutate generated code
- commands that start and stop the same service
- build steps that share cache directories without locks
I've seen teams lose more time debugging flaky CI than they saved by making it faster. A 12-minute stable pipeline is annoying. A 6-minute flaky pipeline is worse because it teaches everyone to distrust the result.
That is the real trade.
Shared state is where the bugs hide
The first thing to audit is file output. Many JavaScript projects write to predictable locations: coverage/, .next/, dist/, build/, .turbo/, or a tool-specific cache folder.
If two commands write there at the same time, the failure may look random. One run passes. The next run fails because a process cleaned a directory while another process was still reading it.
I would check these before changing the workflow:
- Does each command write output?
- Can that output path be changed?
- Does the command need output at all in CI?
- Can reports be written to separate folders and merged later?
For test coverage, make each shard write its own file. For builds, don't run two builds into the same dist directory unless the tool supports it. For generated clients, generate once before the parallel section, then treat the files as read-only.
CI gets nicer when commands behave like good citizens.
Logs need more care when work runs together
Parallel steps can also make a workflow harder to debug. Sequential logs tell a story. Parallel logs can feel like five people talking in the same room.
This is where naming matters more than people admit. Give every parallel step a plain name. Avoid clever labels. A developer who is already annoyed by a failed CI run should be able to scan the page and know what broke.
Good names:
Lint source filesRun TypeScript typecheckRun unit testsCheck formatting
Bad names:
Quality gateStatic analysisValidation phase
The first set tells you where to look. The second set sounds tidy but hides the work.
If your team already uses a code review checklist for small teams, add one CI-specific item: did this change make failures easier or harder to understand?
Fast feedback is useless if nobody can read the failure.
How I would roll it out on a small team
I wouldn't make this a large migration. I'd pick one workflow and change one group of checks.
Start with the pull request workflow, because that is where wait time hurts most. Leave deploy workflows alone until the pattern has proved itself.
My rollout would look like this:
- Record the current median runtime for 10 recent PR runs.
- Pick two or three read-only checks.
- Run them in parallel.
- Watch failures for a week.
- Keep the change only if the logs stay clear and flakiness doesn't rise.
That's it. No grand CI program.
If a check flakes after the change, don't shrug and retry. Treat it as a useful signal. The pipeline just showed you a hidden coupling between commands.
When separate jobs are still better
Parallel steps don't replace jobs or matrices. They solve a narrower problem.
Use separate jobs when checks need different machines, permissions, services, or artifacts. Use a matrix when you need the same command across Node versions, databases, operating systems, or packages.
Use parallel steps when the commands share setup and differ only in what they check.
That distinction keeps the workflow readable. It also keeps costs under control, because repeated dependency installs can quietly become the most wasteful part of a CI system.
This pairs well with architecture decision records if the team keeps arguing about CI structure. Write down why a check is a parallel step, a separate job, or a matrix entry. Future you will forget.
The real win is shorter waiting, not clever YAML
The best version of GitHub Actions parallel steps is boring. Developers push a PR, the main checks run faster, the failure is obvious, and nobody talks about CI in standup.
That's the goal.
If the YAML becomes a puzzle, back off. If the pipeline gets flaky, back off. If nobody can tell which command failed, back off.
Parallelism is useful when it removes waiting without adding guesswork. For CI, that is the whole game.



