# Understanding GitHub Actions Concurrency

When working with GitHub Actions, you may face situations where multiple workflow runs overlap, consume unnecessary resources, or even cause conflicts in deployment. This is where **concurrency** comes into play.

Concurrency in GitHub Actions allows you to control how workflow runs are handled when a new run is triggered before a previous one finishes. By defining concurrency rules, you can decide whether to cancel, queue, or allow multiple runs simultaneously.

---

## Why Concurrency Matters

Imagine the following scenarios:

* **CI pipelines**: A developer pushes multiple commits rapidly. Without concurrency control, each commit triggers a workflow, resulting in unnecessary duplicate builds.
    
* **Deployments**: Two workflows triggered close together may attempt to deploy to the same environment at the same time, leading to inconsistent states.
    
* **Resource Management**: Avoiding multiple parallel runs saves GitHub-hosted runner minutes and keeps pipelines efficient.
    

---

## Basic Concurrency Syntax

You can define concurrency in your workflow YAML file using the `concurrency` key:

```plaintext
name: CI Pipeline

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    concurrency:
      group: ci-build-${{ github.ref }}
      cancel-in-progress: true

    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Build
        run: echo "Running build for $GITHUB_REF"
```

---

## Key Components

### 1\. `group`

* Defines the "bucket" that runs belong to.
    
* Can be static (e.g., `"deploy"`) or dynamic (e.g., `"deploy-${{ github.ref }}"`).
    
* Runs in the same group respect concurrency rules.
    

### 2\. `cancel-in-progress`

* If `true`: cancels any currently running jobs in the same group before starting a new one.
    
* If `false` (default): waits until the current run in the group finishes before starting the next one.
    

---

## Examples

### Example 1: Cancel Previous Runs

Useful for CI pipelines where only the latest commit matters:

```plaintext
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true
```

### Example 2: Sequential Deployments

Ensures only one deployment per environment happens at a time:

```plaintext
concurrency:
  group: deploy-production
  cancel-in-progress: false
```

### Example 3: Matrix Jobs with Concurrency

```plaintext
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [14, 16, 18]
    concurrency:
      group: test-${{ matrix.node }}
      cancel-in-progress: true
```

---

## Best Practices

1. **Use meaningful group names**  
    Use `${{ github.ref }}` or `${{ github.workflow }}` to scope groups appropriately.
    
2. **Cancel aggressively for CI**  
    Saves time and runner minutes when frequent commits occur.
    
3. **Don’t cancel for deployments**  
    Deployment pipelines should finish in order to maintain state consistency.
    
4. **Mix with environments**  
    You can combine concurrency with environment protection rules for more reliable workflows.
    

---

## Docker Example with Concurrency

Suppose you are building and pushing a Docker image on every push to `main`. Without concurrency, multiple builds may try to push at the same time.

```plaintext
name: Docker Build & Push

on:
  push:
    branches:
      - main

jobs:
  docker-build:
    runs-on: ubuntu-latest
    concurrency:
      group: docker-main
      cancel-in-progress: true

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Log in to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and Push
        run: |
          docker build -t myapp:latest .
          docker push myapp:latest
```

Here, if multiple pushes happen rapidly, only the latest Docker build will continue.

---

## Conclusion

Concurrency in GitHub Actions is a powerful feature to:

* Avoid duplicate work,
    
* Prevent deployment conflicts,
    
* Save runner minutes.
    

By properly configuring `group` and `cancel-in-progress`, you can make your pipelines faster, cheaper, and more reliable.
