What developer productivity metrics should describe

Developer productivity metrics should describe how well the delivery system turns intent into running software, not how busy any individual engineer is. Lines of code, commit counts and tickets closed per person measure activity, and they reward the wrong behaviour: splitting work artificially, avoiding reviews, skipping the unglamorous fixes that keep a codebase healthy. Most delays a developer experiences are caused by the system around them: a slow CI queue, a review that sits for a day, a staging environment that has to be booked. Measuring the person hides those causes. Measuring the system exposes them.

This guide uses three complementary views:

  • DORA metrics describe delivery outcomes: how often you ship, how long changes take to reach production, how often they fail, and how quickly you recover.
  • Cycle time broken into stages shows where time actually goes between the first commit and a running deploy, so you can tell waiting apart from working.
  • Developer experience captures what the numbers miss: friction, cognitive load and interruptions, gathered by asking engineers directly.

The guide is written for backend and platform engineers who own pipelines, build tooling and internal platforms. You are the people who can act on what these measures reveal. They are worth collecting for two practical reasons. First, they show where work waits, which is usually where the cheapest improvements are. Second, they give you evidence to justify platform investment in terms that engineering leadership and finance can evaluate, instead of relying on anecdotes about slow builds.

The four DORA metrics and how to read them together

The four DORA metrics give you that shared vocabulary, provided you define each one precisely before you collect any data.

Metric What it measures Category
Deployment frequency How often you successfully release to production Throughput
Lead time for changes Time from a commit to that code running in production Throughput
Change failure rate Share of deployments that cause a production failure needing remediation (rollback, hotfix, patch) Stability
Time to restore service Time from a production failure to recovery Stability

Read these metrics in pairs. A team can raise deployment frequency by skipping review and testing, and that shows up as a rising change failure rate. A team can drive failures toward zero by batching releases, and that shows up as longer lead times. If you track only one side, you reward whichever shortcut moves that side.

The metrics describe the delivery system: pipeline speed, test reliability, review queues and rollback tooling. They do not describe individuals. If you attach them to people, engineers will start optimising their own numbers and stop fixing the system.

These metrics can also be gamed. Splitting one change into several trivial deploys inflates frequency. Narrowing what counts as a "failure" to exclude degraded performance or incidents caught by feature flags lowers the failure rate. Write the definitions down and change them only on purpose.

Finally, DORA metrics tell you how efficiently you ship, not whether the shipped work was worth building. A team can deliver the wrong feature with elite delivery numbers. You still need product and outcome measures, such as adoption, retention or revenue impact, alongside them.

How it works: breaking cycle time into stages

Lead time for changes tells you that work is slow. It doesn't tell you where. Splitting cycle time into stages shows where a change actually sits:

  1. Coding: first commit (or PR open) to review requested.
  2. Pickup: review requested to first review.
  3. Review: first review to approval, including every round of changes.
  4. Deploy: merge to running in production.

Each stage has a different owner and a different fix. A long pickup time usually means review isn't anyone's explicit job, so it gets done between other tasks. Long review usually points to PRs that are too large or requirements that were unclear. A slow deploy stage points at the pipeline, release batching or manual approval gates. If you only track one aggregate number, all of these blur together and you can't tell which fix to try.

Report the median and the 85th or 90th percentile for each stage, not the mean. A single PR that sat open over a holiday can double an average while the typical experience stays the same. The percentile shows how bad the slow end gets, and that is usually what developers complain about.

Compare a team against its own history, not against other teams. Codebases, review norms and risk tolerance differ too much for cross-team rankings to be fair. For a team that merges only a handful of PRs a week, use rolling windows of several weeks, because week-to-week changes are mostly noise.

Finally, tag large work such as migrations, schema changes and dependency upgrades as its own category. If you don't, the metric penalizes careful work that is supposed to take longer, and engineers learn to split it artificially or avoid it.

┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ 1. CODING    │─▶│ 2. PICKUP    │─▶│ 3. REVIEW    │─▶│ 4. DEPLOY    │
│ first commit │  │ review req.  │  │ first review │  │ merge        │
│ to review    │  │ to first     │  │ to approval  │  │ to running   │
│ requested    │  │ review       │  │ (all rounds) │  │ in prod      │
├──────────────┤  ├──────────────┤  ├──────────────┤  ├──────────────┤
│ task size,   │  │ review is    │  │ PRs too big, │  │ pipeline,    │
│ unclear      │  │ nobody's     │  │ unclear      │  │ batching,    │
│ scope        │  │ explicit job │  │ requirements │  │ manual gates │
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       ▼                 ▼                 ▼                 ▼
┌────────────────────────────────────────────────────────────────────┐
│ REPORT EACH STAGE SEPARATELY                                       │
│ - median + p85/p90, not mean (one stalled PR skews a mean)         │
│ - compare each team with its own history, not other teams          │
│ - low-volume teams: rolling multi-week windows (weekly = noise)    │
│ - tag migrations, schema changes, dep upgrades as own category     │
└────────────────────────────────────────────────────────────────────┘

Cycle time split into four stages, each with its own likely cause of waiting, and each reported separately using medians and percentiles, the team's own history, and a separate category for large work.

Implementing the metrics from data you already have

Most teams already have the raw events. Git records commit timestamps, the code host records PR open, first review and merge, CI records pipeline runs, and the deploy tool or its logs record what shipped and when. Incident records supply failures and recovery times. The work is joining these, and that requires a few definitional choices up front.

What counts as a deployment? Pick one: a production rollout that completed for a given service, not a pipeline run or a staging push. What counts as a failure? A deploy that caused a rollback, hotfix or linked incident. Decide whether alerts that were never acknowledged count; most teams exclude them. Where does lead time start? First commit captures the whole cycle but rewards squashing history. Merge is cleaner but hides review delay. Record both and say which one you report.

For git data, git log --first-parent --merges --format='%H %aI %s' on the main branch gives merge commits with ISO timestamps. Export PR and deploy events into one table keyed by commit SHA. Then group by stage (coding, pickup, review, deploy) and compute p50 and p90 durations with your warehouse's percentile function. Use percentiles rather than means, because a few long-lived PRs distort the average.

Budget for ongoing upkeep. Repos merge differently, CI tools emit different event shapes, and teams rename pipelines. Normalizing all of this is permanent platform work. If two teams define "deployment" differently, comparing their numbers is worse than having none.

-- Per-stage lead time percentiles (PostgreSQL syntax; adapt the percentile and
-- epoch functions to your warehouse). Assumes PR and deploy events were exported
-- into two tables keyed by the merge commit SHA, e.g. from:
--   git log --first-parent --merges --format='%H %aI %s' main
WITH prod_deploys AS (
  -- Definition: a deployment is a completed production rollout, not a pipeline run.
  SELECT sha, service, MIN(completed_at) AS deployed_at
  FROM deploy_events
  WHERE environment = 'production' AND status = 'completed'
  GROUP BY sha, service
),
stages AS (
  -- Lead time starts at first commit here; report the merge-based figure separately.
  SELECT 'coding' AS stage, p.opened_at - p.first_commit_at AS dur FROM pr_events p
  UNION ALL
  SELECT 'pickup', p.first_review_at - p.opened_at FROM pr_events p
  WHERE p.first_review_at IS NOT NULL  -- PRs merged without review have no pickup stage
  UNION ALL
  SELECT 'review', p.merged_at - p.first_review_at FROM pr_events p
  WHERE p.first_review_at IS NOT NULL
  UNION ALL
  SELECT 'deploy', d.deployed_at - p.merged_at
  FROM pr_events p JOIN prod_deploys d ON d.sha = p.merge_sha
)
SELECT
  stage,
  COUNT(*) AS n,
  -- Percentiles, not means: a few long-lived PRs would distort an average.
  PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM dur) / 3600) AS p50_hours,
  PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM dur) / 3600) AS p90_hours
FROM stages
WHERE dur IS NOT NULL
GROUP BY stage
ORDER BY CASE stage WHEN 'coding' THEN 1 WHEN 'pickup' THEN 2 WHEN 'review' THEN 3 ELSE 4 END;

A PostgreSQL-style query that joins exported PR events to completed production deploys by commit SHA and computes p50 and p90 hours for the coding, pickup, review and deploy stages.

Developer experience: the third leg

DORA metrics and cycle time describe what the delivery system produces. They say little about what it is like to work inside it, and that experience is often where the next slowdown starts. Three properties matter most:

  • Flow state: stretches of uninterrupted, focused work. Context switches, meetings and waiting on reviews break it, and rebuilding the mental model afterwards takes time.
  • Cognitive load: how much an engineer must keep in their head to make a safe change. Unclear ownership, undocumented services and inconsistent tooling all add to it.
  • Feedback loop speed: how quickly a change tells you whether it worked. Slow loops encourage batching, which makes changes larger and riskier.

Measure these from two directions and compare the results:

Signal type Examples What it catches Limits
Short anonymous survey, quarterly or monthly "How often were you blocked this sprint?" "How easy is it to understand the code you change?" Friction no log records, such as unclear ownership or interruptions Subjective, survey fatigue, low response rates
System signals Local build time, CI duration, flaky test rate, time to provision an environment, time for a new hire to ship a first change Trends, regressions, where time is lost Silent on causes and on how people feel

Use surveys alongside system metrics, never instead of them. If engineers say CI is slow, the CI duration data should show where. If CI looks fast but people still complain, check flaky reruns.

Survey answers are only honest when people trust how the results will be used. If engineers think the data will feed into performance reviews, they give cautious answers and the survey stops being useful. Report results at team level only, and publish what changed because of them.

What to ignore and when to avoid measuring

Some numbers are easy to collect but damage the work once people know they are being watched. Goodhart's law describes the mechanism: when a measure becomes a target, people optimize the number instead of the outcome it was meant to reflect.

Metric Gaming behavior it produces How it distorts decisions
Lines of code Verbose code, copy-paste instead of shared helpers Deleting dead code looks like negative output
Commit counts Tiny, fragmented commits History becomes noisy and harder to bisect
PRs per person Splitting work artificially, avoiding hard tickets Refactors and investigations get deprioritized
Story points across teams Point inflation Estimates stop working for planning within the team
Hours online / activity tracking Staying visibly active, jiggling the mouse Rewards presence over focus; deep work looks idle

Do not measure at all when the data would feed individual performance reviews or cross-team rankings. Once engineers expect consequences, they reshape their behavior and their reporting. Incidents get logged as "maintenance," deploys get batched to look stable, and the dataset stops describing reality. You lose the signal and the trust needed to collect it again.

Even a reasonable team-level metric can push problems elsewhere if you optimize it alone. If you set a target on review turnaround time, reviews get faster partly because they get shallower. Approvals turn into rubber stamps, defects reach production, and change failure rate goes up. The review queue looks healthy while on-call carries the cost. Read any metric you are improving alongside the metric most likely to absorb the damage.

Impact on cost and engineering time: a practical improvement loop

Run the improvement loop on one bottleneck at a time. Pick the stage where the cycle-time breakdown shows work sitting idle longest, write down a hypothesis you could be wrong about, make one targeted change, and give it enough weeks to show past normal noise. Then check two things: whether the stage metric moved, and whether developers say the work feels different. If only one of them moved, you don't have a result yet.

Step Slow CI Long review queue
Signal Build stage dominates cycle time PRs wait days before a first review
Hypothesis Caching dependencies cuts wait time Smaller PRs get picked up sooner
Change Cache dependencies, split slow test suites Set a PR size guideline, rotate reviewers
Check Build duration, survey on waiting Time to first review, change failure rate

Illustrative estimate (assumed numbers, not a benchmark). Assume 40 engineers, 3 CI runs per engineer per day that someone waits on, and a pipeline cut from 18 to 10 minutes, which saves 8 minutes per run. Also assume half the wait is truly idle time, because people switch to other work for the rest of it.

Recovered hours per week = engineers × runs per day × minutes saved × idle fraction × 5 days ÷ 60 = 40 × 3 × 8 × 0.5 × 5 ÷ 60 = 40 hours per week

Change the idle fraction first. It is the least certain input.

Presenting results to leadership

Report the results at team level only. Once a number can be traced to a person, it becomes a target, and people start optimizing the number instead of the work. Present every figure with what changed, what the team expected to happen, and what the developer survey said. Let the engineers involved explain what the numbers mean before anyone draws conclusions from them.

How OneEnv can help

Disclosure: OneEnv is the product behind this blog.

OneEnv (oneenv.io) is a workspace-based governance platform for the things services share — environment variables, database structure, API routes and validation rules. A change to a shared value is staged on a branch, reviewed once, and then every affected service explicitly approves or rejects it before it goes live, so you can see who a change touches before it ships instead of finding out in production.

Applied to developer productivity, this is where it helps:

Saves time

  • Every service that inherits a shared value is listed in one place, so nobody searches repos and chats to find out who is affected.
  • One review per branch replaces coordinating each team separately; each service still keeps its own approve/reject.
  • Reviewers see the exact diff and the list of affected services up front, so approval is a decision rather than an investigation.

Saves cost

  • Fewer production incidents and rollbacks caused by a shared value, route or column changing under a consumer unannounced.
  • Less duplicated configuration to maintain: one shared definition inherited by many services.
  • Per-service approve/reject means a bad shared value can be refused where it would break something, before it is live.
  • OneEnv has a free plan (1 workspace, unlimited services and members, full governance), so trying it costs nothing.

These are qualitative benefits, not measured results — how much you save depends on how many services share the value.

Want to try it? Start at oneenv.io.