CI/CD Interview Questions: GitHub Actions & Pipeline Fundamentals

·10 min read
cicdgithub-actionsdevopsautomationinterview-questions

CI/CD pipelines are central to modern software development. Whether you're applying for backend, full-stack, or DevOps roles, interviewers expect you to understand how code goes from commit to production.

This guide covers CI/CD fundamentals and practical GitHub Actions knowledge that comes up in interviews.

CI vs CD: The Foundation

Q: What's the difference between CI and CD?

This is often the opening question. Many candidates give vague answers.

Weak answer: "CI/CD is automated deployment."

Strong answer:

Continuous Integration (CI):

  • Automatically build and test code when changes are pushed
  • Catch integration issues early (merge conflicts, test failures)
  • Every developer integrates frequently (at least daily)
  • The build is the single source of truth

Continuous Delivery (CD):

  • Code is always in a deployable state
  • Automated pipeline to staging/pre-production
  • Manual approval for production deployment
  • "Could deploy at any time"

Continuous Deployment:

  • Fully automated deployment to production
  • Every passing commit goes live automatically
  • Requires high test coverage and confidence
  • "Do deploy every time"
Push → Build → Test → [Staging] → [Approval?] → Production
       └─────── CI ──────┘    └────────── CD ─────────────┘

What interviewers want to hear: Understanding that CI is about integration quality, while CD is about release readiness. Know the difference between Delivery (manual gate) and Deployment (fully automated).


GitHub Actions: Core Concepts

Q: Explain GitHub Actions workflows, jobs, and steps.

# .github/workflows/ci.yml
name: CI Pipeline                    # Workflow name
 
on:                                   # Triggers
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:                                 # Jobs run in parallel by default
  test:
    runs-on: ubuntu-latest           # Runner environment
    steps:                            # Steps run sequentially
      - uses: actions/checkout@v4    # Action (reusable)
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci                   # Shell command
      - run: npm test
 
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run lint

Hierarchy:

  • Workflow: YAML file in .github/workflows/, triggered by events
  • Jobs: Independent units, run on separate runners, parallel by default
  • Steps: Sequential tasks within a job, share the same runner

Key distinction: Jobs don't share filesystem by default. If job B needs artifacts from job A, you must upload/download them explicitly or use needs: for dependencies.


Workflow Triggers

Q: What events can trigger a GitHub Actions workflow?

on:
  # Push/PR triggers
  push:
    branches: [main, develop]
    paths:
      - 'src/**'                     # Only trigger for src changes
      - '!src/**/*.md'               # Exclude markdown files
  pull_request:
    types: [opened, synchronize, reopened]
 
  # Scheduled (cron)
  schedule:
    - cron: '0 0 * * *'              # Daily at midnight UTC
 
  # Manual trigger
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deploy environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production
 
  # From other workflows
  workflow_call:                      # Reusable workflow
 
  # External events
  repository_dispatch:                # API trigger

Common interview follow-up: "How do you prevent running CI on documentation changes?"

on:
  push:
    paths-ignore:
      - '**.md'
      - 'docs/**'

Job Dependencies and Parallelism

Q: How do you control the order jobs run in?

By default, jobs run in parallel. Use needs for dependencies:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building..."
 
  test:
    needs: build                      # Waits for build to complete
    runs-on: ubuntu-latest
    steps:
      - run: echo "Testing..."
 
  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying to staging..."
 
  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production           # Requires approval
    steps:
      - run: echo "Deploying to production..."

Visual:

build → test → deploy-staging → deploy-production

Parallel with dependency:

jobs:
  lint:
    runs-on: ubuntu-latest
    # ...
 
  test:
    runs-on: ubuntu-latest
    # ...
 
  deploy:
    needs: [lint, test]              # Waits for BOTH
    runs-on: ubuntu-latest
lint  ─┐
       ├→ deploy
test  ─┘

Matrix Builds

Q: How do you test across multiple versions or platforms?

Matrix builds run the same job with different configurations:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
      fail-fast: false                # Don't cancel others if one fails
 
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

This creates 9 jobs (3 OS × 3 Node versions).

Excluding combinations:

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]
    node-version: [18, 20]
    exclude:
      - os: windows-latest
        node-version: 18

Including specific combinations:

strategy:
  matrix:
    include:
      - os: ubuntu-latest
        node-version: 20
        experimental: true

Secrets and Environment Variables

Q: How do you handle secrets in CI/CD?

Never hardcode secrets. Use GitHub Secrets:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: ./deploy.sh

Repository vs Environment secrets:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production           # Uses production-specific secrets
    steps:
      - run: echo "Deploying with ${{ secrets.PROD_API_KEY }}"

Best practices:

  1. Least privilege: Only add secrets to environments that need them
  2. Rotation: Regularly rotate secrets
  3. Never log secrets: GitHub masks them, but be careful with base64/encoding
  4. Use OIDC: For cloud deployments, use OpenID Connect instead of long-lived credentials
# OIDC for AWS (no secret keys needed)
permissions:
  id-token: write
  contents: read
 
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789:role/github-actions
      aws-region: us-east-1

Caching Dependencies

Q: How do you speed up CI pipelines?

Caching avoids re-downloading dependencies:

steps:
  - uses: actions/checkout@v4
 
  - name: Cache node modules
    uses: actions/cache@v4
    with:
      path: ~/.npm
      key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
      restore-keys: |
        ${{ runner.os }}-node-
 
  - run: npm ci
  - run: npm test

Built-in caching with setup actions:

- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'                      # Automatic caching!

What to cache:

  • ~/.npm or node_modules (Node.js)
  • ~/.cache/pip (Python)
  • ~/.m2/repository (Maven)
  • ~/.gradle/caches (Gradle)
  • Docker layers

Cache key strategy:

key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
# Changes when lockfile changes, forcing fresh install

Artifacts: Sharing Between Jobs

Q: How do you pass files between jobs?

Jobs run on different machines. Use artifacts:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
 
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/
          retention-days: 7
 
  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist/
 
      - run: ./deploy.sh dist/

Use cases:

  • Build artifacts (compiled code, bundles)
  • Test reports and coverage
  • Logs for debugging failed runs

Deployment Strategies

Q: Explain blue-green vs canary deployments.

This is a common conceptual question.

Blue-Green Deployment:

┌─────────────┐     ┌─────────────┐
│   Blue      │     │   Green     │
│  (current)  │     │   (new)     │
└─────────────┘     └─────────────┘
       ↑
   Load Balancer
  1. Blue is live, Green is idle
  2. Deploy new version to Green
  3. Test Green
  4. Switch load balancer to Green
  5. Blue becomes idle (instant rollback ready)

Pros: Instant rollback, full testing before switch Cons: Double infrastructure cost

Canary Deployment:

┌─────────────────────────────────┐
│         Load Balancer           │
└───────────┬────────────┬────────┘
            │            │
         95%│            │5%
            ↓            ↓
    ┌───────────┐  ┌───────────┐
    │  Current  │  │  Canary   │
    │  Version  │  │   (new)   │
    └───────────┘  └───────────┘
  1. Deploy new version to small subset
  2. Route 5% of traffic to canary
  3. Monitor metrics (errors, latency)
  4. Gradually increase (10%, 25%, 50%, 100%)
  5. Rollback if metrics degrade

Pros: Catches issues with minimal user impact Cons: Complex routing, longer rollout

Rolling Deployment: Update instances one at a time. Simpler but slower rollback.


Practical Workflow: Full CI/CD Pipeline

Q: Walk through a production CI/CD pipeline.

name: CI/CD Pipeline
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
env:
  NODE_VERSION: '20'
 
jobs:
  # ========== CI ==========
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
 
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npm test -- --coverage
      - uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/
 
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/
 
  # ========== CD ==========
  deploy-staging:
    needs: [lint, test, build]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build
          path: dist/
      - name: Deploy to Staging
        run: ./scripts/deploy.sh staging
        env:
          DEPLOY_TOKEN: ${{ secrets.STAGING_DEPLOY_TOKEN }}
 
  deploy-production:
    needs: deploy-staging
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production           # Manual approval required
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build
          path: dist/
      - name: Deploy to Production
        run: ./scripts/deploy.sh production
        env:
          DEPLOY_TOKEN: ${{ secrets.PROD_DEPLOY_TOKEN }}

Key patterns:

  • Lint/test/build run in parallel (fast feedback)
  • Deploy jobs wait for all CI jobs
  • Staging deploys automatically
  • Production requires manual approval via environment

Reusable Workflows

Q: How do you avoid duplicating workflow code?

# .github/workflows/reusable-deploy.yml
name: Reusable Deploy
 
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      deploy_token:
        required: true
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
      - run: ./deploy.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.deploy_token }}
# .github/workflows/main.yml
jobs:
  deploy-staging:
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: staging
    secrets:
      deploy_token: ${{ secrets.STAGING_TOKEN }}
 
  deploy-production:
    needs: deploy-staging
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: production
    secrets:
      deploy_token: ${{ secrets.PROD_TOKEN }}

Common Interview Questions

"A deployment failed. How do you roll back?"

  1. Immediate: Revert the commit and let CI/CD redeploy
  2. Blue-green: Switch load balancer back to previous environment
  3. Kubernetes: kubectl rollout undo deployment/app
  4. Keep previous artifacts: Redeploy known-good version

"How do you handle database migrations in CI/CD?"

  1. Run migrations before deploying new code
  2. Make migrations backward-compatible (add column, don't remove)
  3. Separate migration job with its own approval
  4. Consider blue-green for database changes

"How do you test infrastructure changes?"

  1. Terraform plan in PR, apply in main
  2. Use staging environment that mirrors production
  3. Infrastructure tests (Terratest, kitchen-terraform)
  4. Require approval for production infrastructure changes

Quick Reference

ConceptPurpose
WorkflowYAML file defining automated process
JobIndependent unit on separate runner
StepSequential task within a job
ActionReusable unit (uses: owner/repo@version)
SecretEncrypted variable for sensitive data
ArtifactFiles passed between jobs
MatrixRun same job with different configs
EnvironmentDeployment target with secrets & approvals

Related Articles

If you found this helpful, check out these related guides:


What's Next?

GitHub Actions covers most CI/CD interview questions, but the concepts transfer to other tools. Once comfortable, explore:

  • GitLab CI - Similar YAML syntax, different features
  • Jenkins - More complex but highly customizable
  • ArgoCD - GitOps for Kubernetes deployments
  • Terraform Cloud - Infrastructure CI/CD

The developers who stand out can explain not just the "how" but the "why"—why we separate CI from CD, why caching matters, why deployment strategies exist.

Ready to ace your interview?

Get 550+ interview questions with detailed answers in our comprehensive PDF guides.

View PDF Guides