What it is
Every construction job starts with a budget, broken down by cost code (concrete, framing, electrical, and so on). As work happens, costs post against those codes and the project manager updates an estimate of what is left to spend.
Job cost variance compares the two views: the budget, adjusted for approved change orders, and the projected final cost. It is usually reviewed for each open job every month, then rolled up by project manager, division, and company.
Why operators track it
- It catches overruns while there is still time to act. An overrun found early can be managed through scope, crew, or change orders. Found late, it is much harder to recover.
- It drives projected margin. If a job will cost more than budgeted and the contract value has not changed, the expected margin shrinks by the same amount.
- It feeds the WIP schedule. Projected cost at completion is also the denominator for percent complete, which drives earned revenue and over/under billing.
- It improves the next bid. Variance by cost code shows estimators which assumptions were wrong.
How it is calculated
Job cost variance = revised budget − estimate at completion
- Revised budget = original budget + approved change order budget
- Estimate at completion (EAC) = cost to date + estimated cost to complete
- Variance % = job cost variance ÷ revised budget
The estimated cost to complete comes from the project manager's forecast, not a formula. That is deliberate: it captures what the PM knows about the work remaining.
Worked example
Job 2417 has an original budget of $1,200,000. Approved change orders add $80,000, so the revised budget is $1,280,000. Cost to date is $910,000 and the PM expects $420,000 more, so EAC is $1,330,000.
How to read it
- Positive means under budget, negative means over under the convention used here (budget minus cost). Some teams flip the sign. Pick one and label every chart.
- The trend matters more than the level. A job sitting at −2% for six months is a different situation from one that moved from 0% to −2% last month.
- Look at cost codes before conclusions. A small total variance can hide a large overrun in one code offset by savings in another.
- We do not publish a "good" range. CFMA's Construction Financial Benchmarker covers related profitability ratios; we will cite it once we confirm citation terms.
Definitional choices and common pitfalls
- Variance at completion vs cost-to-date variance. This page uses budget vs projected final cost. Some teams compare budget × percent complete to cost to date instead, when they trust physical percent complete more than PM forecasts.
- Pending change orders. Including unapproved change orders makes the budget look healthier than it is. Excluding them can make a job look over budget for work the owner will likely pay for. Many teams show pending change orders as a separate column.
- Committed cost. Purchase orders and subcontracts that are signed but not invoiced are not cost to date. They are useful as a check on the cost-to-complete estimate.
- Stale forecasts. If PMs do not update cost to complete, EAC stays flat and overruns stay hidden.
How different roles use it
| Role | How they use it |
|---|---|
| Owner / CEO | Scans the company roll-up and the few jobs with the largest negative variance. |
| CFO / controller | Reviews every open job monthly, checks forecasts are current, and ties variance to projected margin and the WIP schedule. |
| Project manager | Owns the forecast for their jobs and works cost code by cost code to explain and correct variance. |
| Estimator | Reviews variance on finished jobs to adjust unit costs on future bids. |
Questions it answers
- Which open jobs are projected to finish over budget?
- Which cost codes are driving the overrun?
- Did any job move significantly since last month?
- Which PMs' forecasts tend to hold up?
How to present it in a report
- Start with a table of open jobs: revised budget, EAC, variance in dollars, variance as a percent. Sort by largest negative variance.
- Let the reader drill from a job into its cost codes.
- Show the change since last month next to the current value.
- State the sign convention and whether pending change orders are included at the top of the report.
Data you need
Fields
| Field | What it means | Example value |
|---|---|---|
| Job | The project, with its PM and division | Job 2417, PM: R. Diaz |
| Cost code | A category of work in the budget | 03-300 Concrete labor |
| Original budget | Budget for the code when the job started | $320,000 |
| Approved change order budget | Budget added or removed by approved change orders | $0 |
| Cost to date | Posted cost from payroll, AP, and equipment | $270,000 |
| Estimated cost to complete | The PM's forecast of remaining cost, by period | $112,000 |
| Change order status | Pending, approved, or rejected | Approved |
What the raw data looks like
Example data A few rows as they might come out of the source system.
| Job | Cost code | Original budget | Approved CO budget | Cost to date | Cost to complete | Period |
|---|---|---|---|---|---|---|
| 2417 | 02-200 Site work | $170,000 | $10,000 | $160,000 | $8,000 | Oct |
| 2417 | 03-300 Concrete labor | $320,000 | $0 | $270,000 | $112,000 | Oct |
| 2417 | 03-100 Concrete material | $210,000 | $50,000 | $200,000 | $60,000 | Oct |
| 2417 | All other codes | $500,000 | $20,000 | $280,000 | $240,000 | Oct |
Where this data usually lives
- Construction ERP and accounting holds budgets, job cost, change orders, billing, and retainage. For example: Sage 300 Construction and Real Estate, Viewpoint Vista (Trimble).
- Project management often holds change orders and their status, and sometimes budgets and forecasts. For example: Procore.
- Estimating produces the original budget by cost code before the job starts.
- Payroll and time tracking feed labor cost and hours to job cost.
- Spreadsheets often hold the PM's cost-to-complete forecast or the WIP schedule itself.
The same field can live in more than one system. Agree with the customer which one is the source of truth.
Common data gaps and traps
- Change orders not entered. Field work on a change that has not been written up makes the job look over budget.
- Forecasts not updated. If cost to complete is stale, the estimate at completion stays flat and overruns stay hidden.
- Forecasts kept outside the system. Some PMs keep cost to complete in a spreadsheet. Ask where it lives.
- Cost codes that do not match. Actual cost posted to a code with no budget line drops out of a simple join.
- Committed cost mixed into cost to date. Purchase orders and subcontracts not yet invoiced are not cost to date.
Related metrics
- WIP over/under billing: uses EAC to compute percent complete.
- Gross margin by job: variance flows straight into projected margin.
- Labor productivity by cost code: often the cause of labor variance. Coming soon.
- Change order cycle time: slow approvals keep revised budgets out of date. Coming soon.
FAQ
- What is a good job cost variance?
- Zero or positive. The more useful question is whether the variance is changing month over month. Projected margin that keeps fading is a warning sign.
- Is job cost variance the same as cost variance in earned value?
- They are related. Earned value cost variance is earned value minus actual cost to date. Job cost variance at completion compares budget to projected final cost.
- Should committed costs be included?
- Not in cost to date. Committed but uninvoiced costs are better used to sanity check the cost-to-complete estimate.
- How often should job cost variance be reviewed?
- Monthly at minimum, aligned with the WIP schedule. Weekly on large or troubled jobs.
For your data teamFormula variants, tables, SQL sketch, and dashboard spec
Formula details
revised_budget = original_budget + approved_change_order_budget
estimate_at_completion = cost_to_date + estimated_cost_to_complete
job_cost_variance ($) = revised_budget - estimate_at_completion
job_cost_variance (%) = job_cost_variance / revised_budget * 100
- Sign convention: this page uses budget minus cost, so negative = over budget. Some teams flip it. Pick one and label it on every chart.
- Estimated cost to complete comes from the PM's monthly forecast, not from a formula. That is the point: it captures what the PM knows.
Variants
| Variant | Formula | Use when |
|---|---|---|
| Variance at completion (above) | revised budget minus EAC | Monthly job review, WIP |
| Cost-to-date variance | (revised budget x percent complete) minus cost to date | You trust physical percent complete more than PM forecasts |
| Earned value cost variance (CV) | earned value minus actual cost | You run earned value management |
| Cost code variance | same formula at cost code grain | Finding which scope is causing the overrun |
Worked example detail
Job 2417: original budget $1,200,000. Approved change orders add $80,000 of budget. Revised budget: $1,280,000. Cost to date: $910,000. PM estimates $420,000 to complete. EAC = $1,330,000. Job cost variance = 1,280,000 - 1,330,000 = -$50,000, or -3.9% (over budget). Drilling into cost codes shows concrete labor is -$62,000 and site work is +$12,000.
Tables and fields
| Table | Fields | Notes |
|---|---|---|
jobs |
job_id, name, pm_id, division, status, contract_value | |
job_budget |
job_id, cost_code, cost_type, original_budget | Labor, material, subcontract, equipment, other |
change_orders |
co_id, job_id, cost_code, budget_amount, revenue_amount, status, approved_at | Only approved COs move the revised budget |
job_costs |
job_id, cost_code, cost_type, amount, posted_at | AP, payroll, equipment charges |
cost_forecasts |
job_id, cost_code, period, estimated_cost_to_complete | PM monthly forecast |
SQL sketch
WITH budget AS (
SELECT b.job_id, b.cost_code,
SUM(b.original_budget) AS original_budget
FROM job_budget b GROUP BY 1, 2
),
cos AS (
SELECT job_id, cost_code, SUM(budget_amount) AS approved_co_budget
FROM change_orders
WHERE status = 'approved' AND approved_at <= :as_of
GROUP BY 1, 2
),
actuals AS (
SELECT job_id, cost_code, SUM(amount) AS cost_to_date
FROM job_costs WHERE posted_at <= :as_of GROUP BY 1, 2
),
ctc AS (
SELECT job_id, cost_code, estimated_cost_to_complete
FROM cost_forecasts WHERE period = :period
)
SELECT
j.job_id, j.pm_id, j.division, b.cost_code,
b.original_budget + COALESCE(c.approved_co_budget, 0) AS revised_budget,
COALESCE(a.cost_to_date, 0) + COALESCE(f.estimated_cost_to_complete, 0) AS estimate_at_completion,
(b.original_budget + COALESCE(c.approved_co_budget, 0))
- (COALESCE(a.cost_to_date, 0) + COALESCE(f.estimated_cost_to_complete, 0)) AS job_cost_variance
FROM jobs j
JOIN budget b ON b.job_id = j.job_id
LEFT JOIN cos c ON c.job_id = b.job_id AND c.cost_code = b.cost_code
LEFT JOIN actuals a ON a.job_id = b.job_id AND a.cost_code = b.cost_code
LEFT JOIN ctc f ON f.job_id = b.job_id AND f.cost_code = b.cost_code
WHERE j.status = 'open';
-- Note: cost codes with actuals but no budget line need a FULL OUTER JOIN in production.
As a dashboard card
- Card type: table with conditional formatting (jobs as rows; revised budget, EAC, variance $, variance %), plus a drill-down bar chart by cost code.
- Measures:
revised_budget,estimate_at_completion,job_cost_variance,job_cost_variance_pct, all defined once. - Dimensions: job, PM, division, cost code, cost type, period.
- Default view: open jobs, current period, sorted by largest negative variance.
- Per-tenant settings: sign convention, whether pending change orders are included, fiscal calendar. One contractor's controller may want pending COs shown as a separate column; another excludes them. Same governed definition, different saved view.
- Ask it: "Which jobs went further over budget since last month?" compares the same measure across two periods.
Common mistakes
- Comparing actual cost to date against the full budget on a job that is half done. That is not a variance; it is a progress number.
- Forgetting approved change orders, which makes every job with extra scope look over budget.
- Mixing committed cost (POs, subcontracts) into cost to date. Show committed separately.
- Letting PMs leave cost-to-complete stale. A stale forecast hides overruns.