skip to content

In a Jenkins Declarative Pipeline, what does a stage's parallel block do, and what can that stage no longer contain?

level: juniorimportance: should knowfreq 60%

answer

  1. one stage, several branches at once
  2. parallel replaces steps, never joins it
  3. each branch may pick its own agent
  4. failFast aborts the running siblings
  5. one level of nesting in declarative

basics

~20 s

A parallel block runs several nested stages at the same time, each able to pick its own agent. A stage that uses parallel can no longer have its own steps block or its own stages block — parallel replaces them.

solid answer

~50 s

In Declarative Pipeline, `parallel` is one of the mutually exclusive bodies a stage can have: a stage holds `steps`, or nested `stages`, or `parallel`, or `matrix` — never two of them. Inside `parallel` you write ordinary nested stages, and Jenkins starts them all at once. Each branch may declare its own `agent`, `when`, `environment`, `tools` and `post`, so a test stage can fan out across a Linux and a Windows node simultaneously. Branches that do not declare an agent run on the enclosing agent and share its workspace, which is a common source of file clashes. Adding `failFast true` inside the block aborts the remaining branches as soon as one fails; the pipeline-level option `parallelsAlwaysFailFast()` sets that behaviour everywhere. The parent stage finishes only when every branch has finished, and its result is the worst branch result.

code

groovy · 21 lines
groovy
pipeline {
  agent none
  stages {
    stage('Test') {
      failFast true
      parallel {
        stage('Unit') {
          agent { label 'linux' }
          steps { sh 'make unit' }
        }
        stage('Integration') {
          agent { label 'linux' }
          steps { sh 'make integration' }
        }
      }
      post {
        always { echo 'both branches finished' }
      }
    }
  }
}

go deeper

for a junior

Be able to write the block from memory: a stage whose body is parallel, containing nested stages, each with its own steps. Say plainly that parallel replaces the stage's steps block.

for a middle

Explain scheduling: each branch with its own agent takes an executor, branches without one share the enclosing workspace, and the parent stage waits for all branches and takes the worst result.

for a senior

Show the production judgment — diagnose a fan-out that did not speed anything up (executor starvation) or that produced flaky failures (shared workspace), and know when failFast saves real machine time versus hiding failures.

for a principal

Own the tradeoff between fan-out width and fleet capacity: more branches means more executors, more agent provisioning cost and more queueing, and past a point the pipeline gets slower rather than faster. Set the team's convention for where fan-out is worth it.

## What `parallel` is A Declarative Pipeline stage has exactly one body. That body is `steps` (a list of steps to run), `stages` (nested sequential stages), `parallel` (nested stages run concurrently), or `matrix` (nested stages expanded over a set of axis values). Writing two of them in the same stage is a syntax error caught before the build starts. So the direct answer to "what can the stage no longer contain" is: its own `steps`, and its own `stages` — `parallel` takes that slot. ```groovy stage('Test') { failFast true parallel { stage('Unit') { agent { label 'linux' } steps { sh 'make unit' } } stage('Integration') { agent { label 'linux-big' } steps { sh 'make integration' } } } } ``` Note that `failFast true` is written inside the stage that owns the `parallel` block, alongside it. ## What still belongs to the parent stage The parent stage keeps every other directive: `agent`, `when`, `options`, `environment`, `tools`, `input` and `post` are all still legal on it. That is what makes the fan-out usable — you can put a `when` on the parent so the whole fan-out is skipped, or a `post { always { ... } }` that runs once after every branch has completed. ## Agents, executors and workspaces Each branch that declares its own `agent` is scheduled independently and consumes an executor on the node it lands on. If your controller only has two executors free, a five-branch fan-out queues; parallelism in the Jenkinsfile is a request, not a guarantee. Branches that do **not** declare an agent inherit the enclosing agent, which means they run in the *same workspace at the same time*. Two branches both running a build tool that writes to the same output directory will interleave and corrupt each other's files, and the symptom looks like a flaky test rather than a pipeline bug. If branches share an agent deliberately, give each one a distinct output path, or give each branch its own agent so each gets its own workspace. ## `failFast` and how results combine By default every branch runs to completion even after a sibling has failed. That is often what you want for tests — you would rather see all the failures in one build than one at a time. When branches are expensive and one failure already condemns the build, `failFast true` inside the `parallel` block aborts the still-running siblings the moment any branch fails. The aborted branches show as aborted, not failed. Setting `options { parallelsAlwaysFailFast() }` at pipeline level applies that to every parallel block in the file without repeating the flag. The parent stage completes only when all branches have finished (or been aborted), and it takes the worst result of its branches: any failed branch fails the parent stage, and by default that fails the build. ## Nesting limits Declarative allows one level of this: a stage inside a `parallel` block may itself contain `steps` or sequential `stages`, but it may not contain another `parallel`. If you genuinely need a deeper fan-out, drop into a `script` block and use the Scripted form, which has no such restriction: ```groovy parallel( unit: { sh 'make unit' }, integration: { sh 'make integration' }, failFast: true ) ``` In Scripted Pipeline, `parallel` is a step taking a map of branch name to closure, plus the optional `failFast` key. ## Why interviewers ask Fan-out is the first optimisation every team reaches for when the pipeline gets slow, and the two mistakes are always the same: forgetting that branches without their own agent share a workspace, and expecting the build to stop early when nothing asked it to. Being able to say both, plus "the parent stage waits for all of them and takes the worst result", is a complete answer.

  • Two parallel branches in the same stage keep corrupting each other's build output. What is the most likely cause?
    Neither branch declared its own `agent`, so both inherited the enclosing one and ran concurrently in the same workspace. Give each branch its own `agent` — that allocates a separate workspace — or make each branch write to a distinct output directory. Sharing a workspace between concurrent branches is safe only when the work is genuinely read-only.
  • Your Jenkinsfile fans out to eight parallel branches but the build takes as long as before. What would you check?
    Executor availability. Each branch with its own agent needs a free executor on a matching node; if the label only resolves to two agents with one executor each, six branches sit queued and the fan-out serialises. Check the label's node count and executor counts, and the build's queue reasons in the console or the stage view.
  • What does the pipeline option parallelsAlwaysFailFast() change?
    It applies fail-fast behaviour to every `parallel` block in the Jenkinsfile, so you do not repeat `failFast true` in each one. As soon as any branch in any parallel block fails, its siblings are aborted. It is a convenience for pipelines where you never want sibling branches to keep burning executors after a known failure.

saying these in an interview costs you the question

  • Claiming a stage can hold both steps and parallel
  • Assuming parallel branches always get separate workspaces
  • Thinking branches stop automatically when one fails
  • Believing declarative supports unlimited nested parallel blocks
  • Expecting eight branches to run at once regardless of executors

context