At some point, every Jenkins pipeline starts to feel repetitive. You copy-paste steps, duplicate logic across environments, and suddenly your clean CI/CD pipeline turns into a maintenance headache.
This is where loops and reusable steps come in. They let you express intent once and execute it multiple times—cleanly.
Why loops matter in Jenkins pipelines
Imagine deploying the same application to three environments: dev, staging, and production. Without loops, you might write three nearly identical stages.
With loops, you can iterate through environments dynamically. This reduces duplication and makes pipelines easier to evolve.
A quick example (Scripted Pipeline)
Here’s a simple loop in a scripted Jenkins pipeline:
1node {
2 def environments = ['dev', 'staging', 'prod']
3
4 for (env in environments) {
5 stage("Deploy to ${env}") {
6 echo "Deploying to ${env}..."
7 }
8 }
9}This creates dynamic stages for each environment. Clean and scalable.
Declarative pipelines: slightly different approach
Declarative pipelines are more structured, and loops aren’t as straightforward inside stages. However, you can still use Groovy scripting inside script blocks.
1pipeline {
2 agent any
3
4 stages {
5 stage('Deploy') {
6 steps {
7 script {
8 def environments = ['dev', 'staging', 'prod']
9
10 environments.each { env ->
11 echo "Deploying to ${env}"
12 }
13 }
14 }
15 }
16 }
17}This works well, but notice something: you don’t get separate visual stages per environment in Jenkins UI. That’s a common limitation developers run into.
Getting dynamic stages in Declarative pipelines
If visibility matters (and it usually does), you can generate stages dynamically using parallel or nested scripting.
1pipeline {
2 agent any
3
4 stages {
5 stage('Dynamic Deployments') {
6 steps {
7 script {
8 def deployments = [:]
9 def environments = ['dev', 'staging', 'prod']
10
11 environments.each { env ->
12 deployments[env] = {
13 stage("Deploy to ${env}") {
14 echo "Deploying to ${env}"
15 }
16 }
17 }
18
19 parallel deployments
20 }
21 }
22 }
23 }
24}Now Jenkins shows each deployment as its own stage, and they can even run in parallel.
Reusable steps: avoiding repetition entirely
Loops help reduce repetition, but reusable steps take it further.
You can define functions inside your pipeline:
1def deployToEnv(env) {
2 echo "Deploying application to ${env}"
3}
4
5node {
6 ['dev', 'staging', 'prod'].each {
7 stage("Deploy to ${it}") {
8 deployToEnv(it)
9 }
10 }
11}This keeps your pipeline readable and focused on flow instead of implementation details.
Combining loops with real-world tasks
Let’s say you want to run tests across multiple Node.js versions:
node {
def versions = ['14', '16', '18']
for (v in versions) {
stage("Test on Node ${v}") {
sh "nvm use ${v}"
sh "npm install"
sh "npm test"
}
}
}This is far cleaner than duplicating three separate stages.
Where things can go wrong
A common mistake developers make is overusing loops in declarative pipelines without understanding visibility trade-offs.
- No stage visibility: Loops inside
scriptdon’t create UI stages - Harder debugging: Failures may not clearly map to steps
- Parallel pitfalls: Running too many parallel tasks can overload agents
Another subtle issue: variable scoping in Groovy closures. If you're dynamically building stages, always ensure variables are correctly captured.
Performance considerations
Loops themselves are cheap—but what you run inside them isn’t.
Be mindful of:
- Spawning too many parallel builds
- Repeated dependency installations
- Resource contention on agents
If each iteration is heavy, consider batching or caching.
When to use loops vs matrix builds
Jenkins also provides matrix builds, which can sometimes replace loops entirely.
Example use case:
- Testing across multiple OS + language versions
Matrix pipelines give you better visualization and built-in parallelism. Loops are more flexible, but matrix builds are more structured.
A practical pattern that works well
If you’re building production pipelines, a good balance looks like this:
- Use loops for simple iteration logic
- Use functions for reusable steps
- Use parallel blocks for performance
- Use matrix builds when combinations grow complex
This keeps your pipeline expressive without turning it into unreadable Groovy.
Final thoughts
Loops and reusable steps are not just convenience features—they’re essential for scaling Jenkins pipelines.
Once you start thinking in terms of iteration and abstraction, your pipelines become smaller, clearer, and easier to maintain.
And more importantly, you stop dreading edits when your deployment targets inevitably grow.