Bid vs actual material cost analysis is one comparison run at one level of detail: the material dollars you carried per cost code against the material dollars that actually hit that cost code, on jobs that are closed. Not the whole job. Not labor. Materials, by code, on finished work. Everything useful comes out of that one table, and on a shop with clean cost coding it takes about an hour a month.
Most contractors never run it. Not because it is hard, but because it feels like an autopsy. The job is done, the money is spent, and looking at the number cannot get it back. That is exactly backwards. The gap you measure this month is the correction you apply to next month’s bids, and it is the only correction you will ever get that is based on your own purchasing rather than a catalog, a rule of thumb, or a percentage somebody added in 2019 and nobody has touched since.
Four questions make the table useful: what to compare, where each number comes from, how to split the gap into causes you can act on, and what to change once you have it.
What “bid vs actual” actually means on materials
Three numbers get confused constantly, so pin them down before you build anything.
Bid material. The material dollars inside the number you handed the customer. Not the takeoff quantity, not the supplier quote, the dollars you carried. If your estimate carried a contingency or a rounding pad, decide once whether it lives in the bid material figure or outside it, write that decision down, and never change it. A moving definition makes every trend meaningless.
Committed material. Material you have ordered and priced but have not been invoiced for. Purchase orders let you track committed cost and remaining committed cost at the job or cost code level, which matters here because a job that looks under budget three weeks before closeout is usually just a job with unrecorded commitments. Foundation Software puts it plainly: without POs, contractors risk thinking a job is more profitable than it really is.
Actual material. Invoiced dollars posted to that job, including freight, fuel surcharges, core charges, restocking fees on returns, and every counter run that got coded to the job after the fact. This is the number that gets understated, and it gets understated in a predictable direction. Counter runs paid on a card, material pulled from truck stock, and small will-call tickets tend to land in an overhead bucket rather than the job. If half your material by ticket count never touches a PO, your “actual” is fiction and your gap will read artificially small.
Run the comparison only on closed jobs. An open job has commitments outstanding and retention in play, and half-finished data produces half-true conclusions.
The table that does the work
Here is the shape. The numbers are illustrative, not drawn from any real job, and the cost codes are electrical because they make a clean example. Use your own codes.
| Cost code | Bid material | Actual material | Gap | Gap % |
|---|---|---|---|---|
| Wire and cable | $16,800 | $19,135 | +$2,335 | +13.9% |
| Conduit and fittings | $9,200 | $9,940 | +$740 | +8.0% |
| Devices and plates | $4,300 | $4,215 | -$85 | -2.0% |
| Gear and panels | $26,000 | $26,000 | $0 | 0.0% |
| Lighting | $12,500 | $13,900 | +$1,400 | +11.2% |
| Consumables and misc | $2,600 | $3,480 | +$880 | +33.8% |
| Total | $71,400 | $76,670 | +$5,270 | +7.4% |
The total line is the least interesting row on the table. A 7.4% overall gap tells you almost nothing you can act on. The code lines tell you five separate stories:
Gear and panels came in exactly on the number, because it was bought against a job quote that held. That is the control group. It proves the estimating and the coding are both sound, which means the other rows are real signal rather than noise.
Wire and lighting both ran double digits over. Those are the two categories most exposed to commodity movement and to late quoting, so that pattern is expected and it is diagnosable.
Devices came in slightly under, which usually means the takeoff carried a little waste allowance that was not needed. Worth noting, not worth chasing.
Consumables ran a third over on a small base. Small-base categories always look alarming in percentage terms. Read them in dollars first, then in percent.
Build this per job, then stack the same table across every job you closed in a quarter. One job over on wire is weather. Six jobs over on wire is your bid.
Split the gap into price and quantity, because they get fixed differently
An overrun on a cost code has exactly two mechanical causes: you paid more per unit than you carried, or you used more units than you carried. Standard cost accounting splits them, and the split is arithmetic, not judgment.
The materials price variance is (actual price minus standard price) times actual quantity. The materials quantity variance is (actual quantity used minus standard quantity) times standard price. “Standard” here is just your bid unit price and bid quantity.
Take the wire row above, drilled down (again, illustrative):
- Bid: 42,000 ft at $0.40/ft = $16,800
- Actual: 44,500 ft at $0.43/ft = $19,135
- Price variance: ($0.43 minus $0.40) times 44,500 ft = +$1,335
- Quantity variance: (44,500 ft minus 42,000 ft) times $0.40 = +$1,000
- Total: +$2,335
Now the row means something. Fifty-seven percent of the wire overrun is a purchasing and market problem, and forty-three percent is a takeoff or a waste problem. Those go to different people and produce different fixes. If you want the formulas worked through properly, including the sign conventions and where they break, that is covered in material price variance versus quantity variance.
AccountingTools names the usual drivers of an unfavorable price variance directly: rush delivery charges, commodity price swings, supplier pricing power, and buying in volumes different from the estimate. Every one of those is visible on your invoices if you go looking.
Where the price half of the gap comes from
Once you know a code is over on price rather than quantity, there are four candidates, and they are separable.
The market genuinely moved. This is real and it has been large. AGC’s May 2026 analysis reported the producer price index for inputs to new nonresidential construction up 1.8% for the month and 8.4% year over year, the largest annual jump since the pandemic, with copper and brass mill shapes up 26.8% and aluminum mill shapes up 48.8% year over year. The BLS series behind the copper number, copper and brass mill shapes, read 645.990 in January 2025 and 803.275 in June 2026 (preliminary). If your wire price ran over during that window, part of the gap is the metal, and no estimating discipline was going to prevent it.
Your bid price was stale on bid day. A quote you pulled in February, used in April.
The quote did not hold through buyout. Job quotes are scoped to quantities and dates, and material bought past either boundary reprices to stock pricing without anyone calling. That is the most common shape of a quote versus invoice mismatch.
The invoiced price drifted away from your agreed price. Different problem entirely, and the one nobody checks. Same SKU, same supplier, same branch, price walks up over months with no notice. That is material price creep, and it does not show up on any single invoice because no single invoice looks wrong.
Separating market movement from drift is the whole game, because the first one is a bidding correction and the second one is a credit request. The test is a comparison, not an opinion: pull your own unit price history for the SKU and set it against the published index for that material over the same months. If your price rose meaningfully faster than the index, the extra is not the market.
There is a fifth candidate that sits underneath the other four: you may not have been charged your contract price at all. A will-call ticket, an unfamiliar branch, or a purchase on the wrong account can silently drop you onto default pricing, which is the mechanism behind contract price versus counter price. If your overruns cluster on small counter tickets rather than delivered orders, look there first.
The control that catches it before closeout
Bid vs actual on a closed job is a correction to future bids. It cannot save the job you just finished. There is one cheap control that can, and it comes from a contractor on the Mike Holt forums describing the problem in the plainest terms available: “They may quote the stuff on bid day at say $87,000 but when you add all the total invoices up at the completion of the project they can be from $87,000 to $100,000 or more.” The control the same poster gives is a running tally: “keep a running tally on what has been invoiced and when it hits the quote amount flag it”.
That is it. One running total per job per supplier, checked against the quoted amount. When invoiced dollars approach the quote, something is wrong and you still have time to find out what while the job is live and the invoices are fresh. Fresh lines get credited. Old ones get explanations.
The structural version of the same control is matching every invoice to a PO and a receiving document before it gets paid, which catches quantity and price problems at the invoice rather than at closeout. The mechanics of that are in three-way matching for contractors.
The monthly hour
Put this on a fixed day. It does not survive as an ad hoc task.
- Pull the closed jobs from last month. Closed only.
- Export bid material by cost code and actual material by cost code. Two columns, same code list, from your accounting system.
- Check the coding before you read anything. Look for a suspicious overhead materials bucket that grew. Uncoded counter runs are the single most common reason a bid vs actual report lies.
- Compute the gap in dollars and in percent per code. Sort by dollars, not percent.
- Take the top two or three codes by dollars and split them into price and quantity. You need bid unit price, bid quantity, actual unit price, actual quantity for the biggest items in the code, not for every line.
- For price gaps, compare your own unit price trail to the published index for that material over the same months, then decide: market move, stale bid price, expired quote, or drift.
- For anything that reads as drift or as a price you should not have been charged, send the credit request that week. Invoice number, line number, part number, price charged, price and date from your last purchase of the same part.
- Write down the one correction you are making to the next bid. One. A single named change to a single cost code beats a list of nine you will not implement.
An hour is realistic if the coding is clean. If it is not clean, the first two or three months will take longer and the fix is coding discipline, not a better spreadsheet.
What to actually change once you have the number
The temptation after a bad quarter is to raise the material pad across the board. Resist it. A blanket pad prices you out of the work you would have won and still does not cover the categories that actually move. The corrections that hold are narrower:
Correct the codes that move, not the total. If wire runs 12% over on six jobs and everything else lands, your wire pricing is wrong. Fix wire.
Shorten your bid validity on volatile categories, not on everything. Wire, gear, and anything aluminum or copper priced today is a different risk from PVC fittings.
Push more of the material into locked quotes at award. The gear row in the table above came in at zero for exactly this reason. Every dollar you can move from stock pricing to a held job quote is a dollar that stops being a variance.
Price recurring items off your own purchase history rather than a catalog. You already buy that fitting forty times a year. Your own trailing price is a better estimate than any published list, and it is the input to a real estimating system for volatile markets.
Carry escalation as a named exposure where the contract allows it. Procore’s overview of escalation clauses notes that recovery is either index-based or based on documented actual cost, and that either route requires you to have retained the initial quotes, bid proposals, purchase orders and invoices to support the claim. The document trail you build for bid vs actual is the same trail that supports an escalation claim. Build it once, use it twice.
One honest caveat about precision. Estimate accuracy is bounded by how well the project was defined when you priced it. AACE’s estimate classification system uses maturity of project definition as the sole primary determinant of estimate class, and is explicit that accuracy ranges are indicative rather than fixed. A design-build job priced off a napkin is not going to hit the same variance as a hard-bid job off complete drawings, and holding both to the same target teaches your estimators to pad. Track the gap separately by how well defined the job was when you bid it.
The short version
- Compare bid material to actual material, per cost code, on closed jobs only.
- Check your coding first. Uncoded counter runs make the gap look smaller than it is.
- Split the biggest gaps into price variance and quantity variance. They have different owners.
- Separate market movement from price drift by setting your own unit price trail against the published index for that material.
- Keep a running invoiced-versus-quoted tally on live jobs so you catch it before closeout.
- Change one named thing per review, on the codes that actually moved.
- Track the gap separately by how well defined the job was when you bid it.
Sources
- Prices for Construction Materials Climb at Highest Rate Since the Pandemic, AGC of America
- PPI: Copper and Brass Mill Shapes (WPU102502), U.S. Bureau of Labor Statistics
- Materials Price Variance, AccountingTools
- Direct Materials Variances, Penn State Managerial Accounting
- Job Costs and Purchase Orders, Foundation Software
- Escalation Clause, Procore
- Cost Estimate Classification System, AACE International
- Ethics of Sharing Competing Supply House Prices, Mike Holt forums