Cut Delivery Time by 37% with Pragmatic Kanban

kanban

Cut Delivery Time by 37% with Pragmatic Kanban
Flow-focused tactics, configs, and metrics that actually shrink queues.

Flow Beats Firefighting: Why Kanban Works in DevOps
If we’ve ever watched a sprint board turn into a digital junk drawer, we know why kanban keeps showing up in healthy delivery teams. Kanban doesn’t ask us to change our process overnight; it asks us to make our current process visible, limit how much we juggle, and then continuously remove friction. That’s why it lands so well in DevOps, where work flows across build, review, deploy, observe, and incident response. When we make that flow explicit, measurement stops being a guessing game and starts being a lever. Lead time improves not by pep talks but because queues shrink and handoffs get clearer. The biggest aha moment usually arrives when we apply Little’s Law: average WIP equals throughput times lead time. If lead time is stubborn, we reduce WIP; if throughput is spiky, we stop starting and start finishing. We’ve seen teams shave 37% off lead time in a quarter by doing nothing fancier than setting realistic WIP limits and tightening pull policies. This isn’t magic; it’s physics. If we care about outcomes, kanban also plays nicely with the widely used engineering health metrics—deployment frequency, lead time for changes, change failure rate, and MTTR—popularized by the folks at DORA. And if we want to go deeper into the queueing math without melting our brains, this ACM Queue piece on Little’s Law is a great explainer. In short, kanban isn’t a board with columns; it’s a small set of agreements that force us to finish the right thing next, reduce avoidable rework, and make capacity a conversation grounded in data instead of optimism.

Design a Board That Mirrors Reality
A board that mirrors reality beats a board that mirrors the org chart. We want columns that represent actual states work passes through, not vague moods. If our pipeline is Build → Review → Test → Deploy → Observe, then our board should reflect that, with clear entry/exit policies per column. It’s tempting to create every possible micro-state, but more columns don’t equal more control; they equal more places for work to idle. A good board has just enough granularity to spotlight queues and just enough policy to keep it honest. For a distributed team, we’ll also want explicit definitions for when a card is “ready” to enter a column and what must be true to exit. Here’s a simple, tool-agnostic config sketch we can translate to Jira, GitHub Projects, or Azure Boards:

kanban:
  columns:
    - name: Ready
      wip: 8
      entry_policy: "Acceptance criteria written; dependencies identified"
      exit_policy:  "Developer assigned; design reference linked"
    - name: In Progress
      wip: 6
      entry_policy: "Ready; unblocked"
      exit_policy:  "Code compiles; unit tests pass; PR opened"
    - name: Review
      wip: 4
      entry_policy: "PR open; checks green"
      exit_policy:  "2 approvals; comments resolved"
    - name: Test
      wip: 4
      entry_policy: "Merged to trunk or release branch"
      exit_policy:  "Integration tests green; rollback plan noted"
    - name: Deploy
      wip: 2
      entry_policy: "Change approved; window available"
      exit_policy:  "Live; smoke checks pass; ticket moved to Observe"
    - name: Observe
      wip: 6
      entry_policy: "Deployed"
      exit_policy:  "KPIs stable 24h; post-deploy notes written"

The “Observe” column keeps post-deploy reality in the flow, so the board doesn’t declare victory the second a green button lights up. And yes, finishing includes the boring notes. Future us will thank present us.

Set WIP Limits That Bite (Not Bruise)
WIP limits should be tight enough to expose pain but not so tight they freeze the team. If WIP = throughput * lead_time and we want to cut lead time by a third without tanking throughput, we reduce WIP proportionally and let the constraint push better behaviors. As a starting point, set column WIP limits slightly below the number of people who typically work there, especially for Review and Test, where invisible queues hide. If we have four reviewers, try a Review WIP of 3. It’ll force finishing and pairing. Expect grumbling; grumbling is the sound of queues shrinking. To keep this scientific, track aging WIP: how long each card’s been in its current column. Aging tells us when WIP limits are wrong or policies are fuzzy. If we already run Prometheus, it takes little effort to export a custom gauge per card that captures kanban_card_age_seconds{column="Review"} and pair that with alerts. A simple PromQL query to highlight risky work might look like:

topk(10, kanban_card_age_seconds{column=~"Review|Test"})

Or, to watch the average age in Review:

avg_over_time(kanban_card_age_seconds{column="Review"}[1h])

When we start seeing cards aging beyond our service level expectation (say, 2 days in Review), we either add capacity, reduce intake, or sharpen policies. The goal isn’t to hit a number—it’s to surface the conversations that prevent slow drift into “stuck” becoming normal.

Make Pull Policies Explicit and Uncheatable
Pull beats push, but only if we’re disciplined about what “ready to pull” means. Ambiguity breeds half-starts, which breed WIP inflation, which breeds sadness. We like two lightweight artifacts: entry/exit policies per column and classes of service per card. Entry/exit policies stop us from pulling half-baked work into the next stage. Classes of service make exceptions visible and rare. “Expedite” should mean something like “customer-impacting incident,” not “someone pinged a VP.” We can codify both in our board’s automation so that transitions are blocked until policies are met. Here’s a compact JSON policy we’ve used with lightweight scripts:

{
  "columns": {
    "In Progress": {
      "entry": ["acceptance_criteria", "design_link", "unblocked"],
      "exit":  ["unit_tests_green", "pr_open"]
    },
    "Review": {
      "entry": ["pr_open", "checks_green"],
      "exit":  ["two_approvals", "comments_resolved"]
    },
    "Test": {
      "entry": ["merged_to_trunk_or_release"],
      "exit":  ["integration_green", "rollback_plan"]
    },
    "Deploy": {
      "entry": ["change_approved", "window_available"],
      "exit":  ["smoke_green", "observability_dashboard_link"]
    }
  },
  "classes_of_service": {
    "standard":    {"wip_multiplier": 1},
    "fixed_date":  {"due_date_required": true, "wip_multiplier": 1},
    "expedite":    {"cap": 1, "requires_incident_id": true, "wip_multiplier": 0},
    "intangible":  {"review_on_mondays": true, "wip_multiplier": 1}
  }
}

The wip_multiplier makes exceptions explicit: expedites don’t consume WIP because they’re interrupts with real costs, tracked separately—otherwise we’d normalize chaos. The important part isn’t the syntax; it’s that we agree on rules we can’t quietly ignore. When “Ready” actually means ready, flow accelerates without anyone moving faster.

Instrument Flow: From Lead Time to Aging WIP Alerts
We can’t steer what we don’t measure. Cycle time (first touch to live) and lead time (requested to live) are our primary gauges, but we also want throughput, WIP, and aging WIP. The cleanest pattern we’ve found is to emit events at key transitions—card enters Review, PR merged, deployed to environment X—then compute durations in our metrics pipeline. With GitHub, a simple webhook listener can record timestamps when PRs open, checks pass, merges happen, and deploy tags land. We export gauges and summaries that Prometheus can scrape, then build a Grafana board that shows 7/30/90-day trends. For alerting, we prefer a couple of low-noise checks: aging WIP, review queue depth, and deploy fail rate. Here’s a basic Prometheus alert that pages a Slack channel if the average age in Review blows past two days for more than an hour:

groups:
- name: kanban.rules
  rules:
  - alert: ReviewAgingWIPHigh
    expr: avg_over_time(kanban_card_age_seconds{column="Review"}[1h]) > 172800
    for: 1h
    labels:
      severity: warning
    annotations:
      summary: "Review aging WIP > 2d"
      description: "Average age in Review exceeded 2 days over the last hour."

Tie this to a burn chart and we’ll spot when reviews starve or testers drown long before lead time gets ugly. If we need a mental model for alerting that won’t wake us at 3 a.m. for nothing, the guidance in the SRE Workbook maps neatly to kanban signals: alert on symptoms (aging WIP), not every twitch.

Automate Hygiene: Bots That Enforce the Boring Stuff
A tidy kanban system dies the moment it depends on good intentions. Let’s let small bots do the nagging. We can enforce WIP limits, stop cards from jumping columns without satisfying policies, and auto-label or escalate stale work. If our board lives in GitHub, a simple Action can block new “In Progress” labels once someone hits their personal WIP cap. This keeps context-switching in check and makes capacity a team conversation, not a solo sport. Here’s a minimal workflow that fails when the actor has too many “In Progress” issues assigned. It uses the Search API to count open issues with the label and refuses to proceed when the count meets or exceeds our limit:

name: wip-check
on:
  issues:
    types: [assigned, labeled, reopened]
  pull_request:
    types: [opened, ready_for_review, reopened]
jobs:
  enforce-wip:
    runs-on: ubuntu-latest
    env:
      WIP_LIMIT: "3"
    steps:
      - name: Count assigned in-progress issues
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          q="is:issue state:open label:'In Progress' assignee:${{ github.actor }} repo:${{ github.repository }}"
          count=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \
            "https://api.github.com/search/issues?q=$(python3 -c 'import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))' "$q")" \
            | jq '.total_count')
          echo "Current WIP: $count (limit: $WIP_LIMIT)"
          if [ "$count" -ge "$WIP_LIMIT" ]; then
            echo "WIP limit reached. Finish something before starting new work."
            exit 1
          fi

We can expand this to check column entry policies by verifying required labels or checklist items before allowing a transition. The GitHub REST API makes it straightforward to keep us honest without building a Rube Goldberg machine. Small guardrails beat big lectures.

Put Review and Test On a Diet, Not a Pedestal
Most teams don’t bottleneck in coding; they bottleneck in Review and Test. If we only optimize developer throughput, we’ll just feed those queues faster. Let’s treat Review and Test like first-class citizens with their own capacity plans, pairing, and swarming norms. One approach that works well is dedicating daily “review hours” where everyone who can review drops what they’re doing and clears the queue to zero, starting with the oldest and riskiest items. If needed, we co-review on a call to speed it along and share context. We also remove invisible work by asking for smaller, independent changes—if a card can’t move to Review within a day of starting, it might be too big. Batch size is the silent killer of flow, and kanban makes that visible. Testing gets the same love: short feedback loops, reliable test environments, and a clear definition of “integration done.” If “waiting for environment” shows up as a frequent blocker, we budget time to fix the pipeline and consider ephemeral environments over static shared ones. Hand-offs get entry/exit policies like any other column, so “Test” doesn’t become “mystery time.” Finally, we honor the operations half of DevOps: post-deploy observation is part of “done,” not a courtesy ping. When we build habits that drain the middle columns daily, lead time falls without anyone pulling weekends.

Scale Without Diluting: Portfolio Kanban and SLOs
Once the team board is humming, the next challenge is scaling without slipping back into project Tetris. Portfolio kanban lets us visualize the big work—initiatives, epics, cross-team efforts—flowing across a few shared states: Shaping, Ready, In Progress, Validate, Done. The trick is to keep it operational, not theatrical. Each lane maps to real capacity, and each portfolio item breaks down into child work that appears on team boards, so we can trace delivery without weekly parade slides. WIP limits still apply, often brutally; having twelve initiatives “in flight” doesn’t impress customers if nothing lands. We also connect portfolio kanban to service-level objectives. If an initiative exists to improve SLO error budgets, we treat incidents and reliability work as first-class portfolio items with their own WIP, not “interrupts” we pretend don’t exist. That means a dedicated lane for incident follow-ups with fixed-date class of service, and an expedite lane reserved strictly for active incidents. When the portfolio board shows too many fixed-date items clustering, we have an honest conversation about trade-offs early instead of sprint 6 autopsies. Finally, we hold regular, short replenishment and operations reviews at both levels. At the team level, we pull the next most valuable ready item. At the portfolio level, we decide which bets deserve focus now, and which must wait. The flow lens scales; the ceremonies stay lightweight; the physics don’t change.

Share