← Back to Learning Hub

Emission Factors & Carbon Accounting

Grid carbon intensityEmission factorsIntermediate16 min

By: Anacodic Team

TL;DR — An emission factor EF is how much CO₂ a source emits per kWh (gCO₂/kWh). It's the number that turns "how much electricity" into "how much carbon." Two choices decide whether your carbon report is honest or misleading: lifecycle vs combustion-only factors (do you count building the plant and supplying the fuel?), and average vs marginal accounting (are you measuring the whole mix or the one generator your decision actually moves?). The average/marginal choice can flip a conclusion. The professional move: report the transparent average basis and disclose the marginal caveat.


1. Simple explanation

An emission factor is a conversion rate. It answers: "For one kWh made this way, how many grams of CO₂?"

Coal is about 820 gCO₂/kWh. Wind is about 11. That's it — a per-kWh price tag in carbon. Multiply the factor by the kWh, and you get grams of CO₂.

But two subtle questions decide whether your number means anything.

Question 1: What do you count? Do you count only the smoke from the chimney (combustion-only)? Or also the concrete poured to build the plant, the steel, the mining and piping of the fuel, and decommissioning (lifecycle)? For wind and solar there's no chimney at all, so combustion-only would say "zero" — which is wrong, because building the panels and turbines took energy. Lifecycle is the honest, complete count.

Question 2: Average or marginal? If you ask "how dirty is the grid on average right now?" you average the whole mix. But if you ask "if I plug in one more server, which power plant turns up to feed it?" — that's usually a gas peaker, not the average. These two answers can point in opposite directions.

Analogy — a shared dinner bill. Lifecycle vs combustion is like asking whether the bill includes just the food, or also the tip, tax, and the cost of the kitchen. Average vs marginal is the deeper one: the average cost per person is the total bill split evenly — but if you order one more dessert, the marginal cost is just that dessert's price, not the average. Deciding whether to order dessert by looking at the average per-head cost gives you the wrong answer. Same with adding load to a grid: judge it by the marginal plant it turns on, not the average mix.


2. Diagram

   ENERGY (kWh)  ──×──  EMISSION FACTOR (gCO2/kWh)  ──=──  CARBON (gCO2)
                          │
        ┌─────────────────┴──────────────────┐
        │                                     │
   WHAT DO YOU COUNT?                   WHICH FACTOR?
   (scope of the factor)               (basis of the decision)
        │                                     │
   ┌────┴─────┐                        ┌──────┴───────┐
   COMBUSTION  LIFECYCLE               AVERAGE        MARGINAL
   only the    build + fuel            whole-mix      the ONE plant
   chimney     + run + end             CI(t)          your +load turns on
   (wind=0!)   (wind=11, honest)       transparent    often GAS (490)
                                       reproducible   truer for decisions

   ACCOUNTING FRAME (Scope 2):
        LOCATION-BASED  = physical grid mix where you plug in  (CI of the wire)
        MARKET-BASED    = contracts/RECs/PPAs you bought       (what you paid for)

3. How it works

3.1 The emission-factor table (IPCC AR5 lifecycle)

We use the IPCC AR5 lifecycle medians. "Lifecycle" = cradle-to-grave: construction, fuel supply, operation, decommissioning.

FuelEF (gCO₂/kWh)Why that number
Coal820High carbon fuel + combustion dominates
Oil650Liquid fossil, peaker/backup
Gas490Burns cleaner than coal; still fossil
Other≈230Biomass/mixed/unknown fallback
Solar48No combustion — this is panel manufacturing
Hydro24Construction + some reservoir methane
Nuclear12Tiny lifecycle footprint
Wind11Lowest at scale — mostly turbine build

3.2 Why lifecycle differs from combustion-only

Combustion-only counts just the CO₂ from burning fuel at the plant. It has one fatal flaw:

   Combustion-only says:   SOLAR = 0,  WIND = 0,  NUCLEAR = 0   (no fuel burned!)
   Lifecycle says:         SOLAR = 48, WIND = 11, NUCLEAR = 12  (build + supply chain)

Combustion-only makes renewables look perfectly clean, which overstates their benefit and hides the real (small) footprint of manufacturing panels and turbines. Lifecycle is the honest basis for comparing sources, because it counts the carbon that a smokestack-only view ignores. For fossil fuels the two are closer (combustion dominates), but for renewables and nuclear the entire footprint is lifecycle — so mixing bases in one report is a classic error.

Combustion-onlyLifecycle (AR5)
CountsOnly burning at the plantBuild + fuel supply + run + end-of-life
Renewables/nuclear~0 (misleading)11–48 (honest)
Fossil fuelsSlightly lowerSlightly higher
Use forRegulatory stack emissionsComparing sources fairly

Rule: never mix bases in one calculation. Pick lifecycle or combustion-only and label it.

3.3 Average vs marginal — the single most important nuance

This is the concept interviewers use to separate people who repeat carbon numbers from people who understand them.

Average emission factorMarginal emission factor
Question it answers"How dirty is the whole mix right now?""If I add/remove load, which plant responds?"
How computedWeighted average of all fuels (CI(t))The generator on the margin (often gas)
Best forReporting total footprint, transparencyDecisions: should I add/shift this load?
Data sourceEIA fuel mix, ISO feedsElectricity Maps, WattTime
RiskUnder/over-credits a marginal decisionHarder to measure; model-dependent

The average CI is the whole-mix intensity from the Why Grid Carbon Intensity Varies Hour to Hour article. But when you decide to run one more job, you don't spread it across the average mix — you turn up whatever plant is on the margin, which is very often a gas turbine (~490), because the cheap clean baseload (wind, nuclear) is already fully dispatched.

3.4 How a conclusion can FLIP (see §4.2 for numbers)

Imagine a grid that is 80% nuclear + 20% gas one hour. The average CI is low (~110), so an average-basis report says "the grid is clean, running load now is fine." But the nuclear is baseload — it's already maxed. Any extra load spins up gas at 490. On a marginal basis, adding load here is 4–5× dirtier than the average suggests. A decision that looks green on average is actually dirty on the margin. Same hour, opposite verdict.

3.5 Scope 2: location-based vs market-based

The GHG Protocol requires companies to report Scope 2 (purchased electricity) two ways:

   LOCATION-BASED  = your kWh × the CI of the physical grid you're plugged into
                     (reflects the actual electrons — the wire's real mix)
   MARKET-BASED    = your kWh × the factor of what you CONTRACTED for
                     (RECs, PPAs, green tariffs — "I bought wind power")

A company can buy Renewable Energy Certificates (RECs) and report ~0 market-based emissions while its physical grid (location-based) is still burning gas at 6 p.m. Both are legitimate and required — they answer different questions ("what flowed through my wire" vs "what did I pay to support"). Reporting only the flattering one is greenwashing.

3.6 Where the data comes from

SourceWhat it givesBasis
EIA Open Data APIHourly fuel mix by balancing authority (US)Average (you compute CI)
ISO feeds (ISO-NE, PJM, CAISO…)Real-time generation mixAverage
Electricity MapsCI + marginal signal, many countriesAverage and marginal
WattTimeMarginal operating emissions rate (MOER)Marginal
IPCC AR5The EF per fuelLifecycle factors

4. The math

4.1 Emission factor → carbon

The core identities:

   carbon (gCO2)         = energy (kWh) × EF (gCO2/kWh)
   CI_average(t)         = Σ_f gen_f(t)·EF_f / Σ_f gen_f(t)    (whole-mix, gCO2/kWh)
   marginal carbon(Δload)= Δload (kWh) × EF_marginal           (the plant that responds)

4.2 Worked example — the conclusion flip

Grid this hour: NUCLEAR 8000 MWh + GAS 2000 MWh. You want to add a 100 kWh job.

Average basis (whole mix):

   CI_avg = (8000·12 + 2000·490) / (8000 + 2000)
          = (96,000 + 980,000) / 10,000
          = 1,076,000 / 10,000 = 107.6  gCO2/kWh   → looks CLEAN
   job carbon (average basis) = 100 kWh × 107.6 = 10,760 gCO2 ≈ 10.8 kg

Marginal basis (nuclear is maxed baseload; extra load spins up gas):

   EF_marginal = 490  (gas, the responding unit)
   job carbon (marginal basis) = 100 kWh × 490 = 49,000 gCO2 = 49.0 kg

The flip:

   marginal / average = 49.0 / 10.8 ≈ 4.5×

   Average basis says: "grid is clean (107.6), your job ≈ 10.8 kg — go ahead."
   Marginal basis says: "your job actually spins up GAS — it's ≈ 49 kg, 4.5× worse."

Same hour, same job, opposite guidance. This is why you disclose the basis. For a decision (add/shift load), the marginal number is the more honest one; for a total footprint report, the transparent average is standard. Report the average, flag the marginal caveat.


5. Real code

A factors table plus a function that annotates a CI result with its accounting basis — so no number leaves your pipeline without a label.

"""Emission factors + an annotator that stamps every CI number with its basis.
The point: a carbon figure is meaningless without (scope, basis, marginal caveat).
"""
from dataclasses import dataclass

# IPCC AR5 LIFECYCLE emission factors (gCO2/kWh). Lifecycle, not combustion-only.
EMISSION_FACTORS_LIFECYCLE = {
    "COAL": 820, "OIL": 650, "GAS": 490, "OTHER": 230,
    "SOLAR": 48, "HYDRO": 24, "NUCLEAR": 12, "WIND": 11,
}
# The fuel usually 'on the margin' (responds to +/- load) on most US grids.
DEFAULT_MARGINAL_FUEL = "GAS"


@dataclass
class CarbonEstimate:
    value_gco2_per_kwh: float   # the CI or EF number
    basis: str                  # "average" or "marginal"
    scope2_method: str          # "location-based" or "market-based"
    lifecycle: bool             # True = includes build+fuel supply
    caveat: str                 # human-readable honesty note

    def __str__(self) -> str:
        life = "lifecycle" if self.lifecycle else "combustion-only"
        return (f"{self.value_gco2_per_kwh:.1f} gCO2/kWh "
                f"[{self.basis}, {life}, Scope2:{self.scope2_method}] — {self.caveat}")


def annotate_ci(ci_value: float,
                basis: str = "average",
                scope2_method: str = "location-based",
                lifecycle: bool = True,
                marginal_fuel: str = DEFAULT_MARGINAL_FUEL) -> CarbonEstimate:
    """Wrap a raw CI number with its accounting basis and an honest caveat."""
    if basis == "average":
        caveat = (f"whole-mix AVERAGE; a decision to ADD load likely displaces the "
                  f"MARGINAL unit ({marginal_fuel}="
                  f"{EMISSION_FACTORS_LIFECYCLE[marginal_fuel]}), which can differ a lot")
    elif basis == "marginal":
        caveat = ("MARGINAL rate for add/shift decisions; not the whole-mix average, "
                  "and model-dependent (source: Electricity Maps / WattTime)")
    else:
        raise ValueError(f"basis must be 'average' or 'marginal', got {basis!r}")
    return CarbonEstimate(ci_value, basis, scope2_method, lifecycle, caveat)


if __name__ == "__main__":
    # Report the transparent average, but ALWAYS surface the marginal caveat.
    avg = annotate_ci(107.6, basis="average")
    mrg = annotate_ci(490.0, basis="marginal")
    print(avg)
    print(mrg)
    # 107.6 gCO2/kWh [average, lifecycle, Scope2:location-based] — whole-mix AVERAGE; a
    #   decision to ADD load likely displaces the MARGINAL unit (GAS=490), ...
    # 490.0 gCO2/kWh [marginal, lifecycle, Scope2:location-based] — MARGINAL rate for
    #   add/shift decisions; not the whole-mix average, ...

The load-bearing idea: a bare float like 107.6 is dangerous. CarbonEstimate forces every number to carry its basis, scope, lifecycle flag, and a caveat — so downstream readers can't quietly treat an average as if it were marginal.


6. Real-world example

Scenario: a cloud team reports its data-center carbon — and gets challenged in review.

The team runs 100,000 kWh/month in a region whose average CI is 107.6 gCO₂/kWh (nuclear-heavy). They report:

ReportBasisNumberMonthly carbon
First draftAverage, location-based107.610.76 tonnes
Sustainability auditMarginal (added load → gas 490)49049.0 tonnes
Market-based (bought RECs)Contracted wind (~11)111.1 tonnes
   Same 100,000 kWh, THREE legitimate numbers: 1.1 t, 10.76 t, 49.0 t — a ~45× spread.
   None is a lie. Each answers a DIFFERENT question:
     market-based (1.1 t)  = "what did we pay to support?"  (RECs/PPAs)
     location-based avg    = "what's the wire's mix?"       (transparent default)
     marginal (49 t)       = "what did our EXTRA load turn on?"  (decision-honest)

The honest report leads with the location-based average (10.76 t) as the transparent basis, states the market-based (1.1 t) separately per GHG Protocol, and discloses that on a marginal basis new load displaces gas (~49 t) — so growth decisions should use the marginal signal. That disclosure is what turns a defensible report into a trustworthy one, and it's exactly the sensitivity-analysis mindset from Backtesting, Baselines & Sensitivity Analysis: show how the answer moves when you change the assumption.


7. Interview questions companies actually ask

Q1 [easy] (Amazon, Google) "What is an emission factor?"
  A A conversion rate: gCO2 emitted per kWh for a given source. Multiply kWh by the factor to
    get carbon. Coal ~820, gas ~490, solar ~48, wind ~11 (IPCC AR5 lifecycle).

Q2 [easy] (Microsoft) "Why isn't wind's emission factor zero?"
  A Because we use LIFECYCLE factors. Wind burns no fuel (combustion-only = 0), but building
    the turbine — steel, concrete, transport — has a footprint. Lifecycle honestly counts it
    at ~11 gCO2/kWh. Combustion-only would wrongly say 0.

Q3 [medium] (Google, Salesforce) "Lifecycle vs combustion-only — when does the choice matter
   most?"
  A Most for renewables and nuclear, where combustion-only is ~0 but lifecycle is 11-48 —
    a huge relative gap. For fossil fuels the two are close (burning dominates). Never mix
    bases in one report; pick one and label it.

Q4 [medium] (WattTime, Electricity Maps) "Average vs marginal emission factors — what's the
   difference and which do you use for a scheduling decision?"
  A Average = the whole-mix intensity right now. Marginal = the one generator that responds
    when you add/remove load (often gas). For a DECISION to add or shift load, marginal is
    more honest, because your extra kWh spins up the marginal plant, not the average mix.

Q5 [medium] (Amazon, Meta) "Give an example where average and marginal give opposite advice."
  A An 80% nuclear / 20% gas hour: average CI ~108 looks clean, so 'run now' seems green. But
    nuclear is maxed baseload; extra load spins up gas at 490 — ~4-5x dirtier on the margin.
    Average says 'go', marginal says 'this is actually dirty'. Same hour, opposite verdict.

Q6 [medium] (Deloitte, sustainability roles) "Explain Scope 2 location-based vs market-based."
  A Location-based = your kWh × the physical grid's CI (the actual electrons). Market-based =
    your kWh × what you contracted for (RECs/PPAs/green tariffs). GHG Protocol requires BOTH;
    reporting only the flattering market-based number while the wire burns gas is greenwashing.

Q7 [hard] (Google, climate-tech) "A team buys RECs and reports ~zero emissions. Is the grid
   any cleaner? How do you audit this?"
  A Not necessarily. RECs zero out the MARKET-BASED number but the LOCATION-BASED (physical)
    grid may be unchanged, and on a MARGINAL basis new load still turns on gas. Audit by
    reporting all three bases and checking whether the RECs are additional (funded new clean
    capacity) or just paper claims on existing generation.

Q8 [hard] (research/lab roles) "How do you keep a carbon report intellectually honest?"
  A Lead with the transparent, reproducible average location-based number; state the
    market-based number separately per GHG Protocol; and disclose the marginal caveat for any
    add/shift decision. Label every figure with (basis, lifecycle-or-combustion, scope).
    Never let a bare number travel without its assumptions — and show a sensitivity range.

8. When to use / tradeoffs

   USE AVERAGE (whole-mix CI) when:
     ✓ reporting total footprint of existing consumption
     ✓ you need transparency + reproducibility (EIA/ISO data, auditable)

   USE MARGINAL when:
     ✓ DECIDING to add, shift, or defer a flexible load
     ✓ you have a marginal signal (Electricity Maps / WattTime)

   USE LIFECYCLE factors when:
     ✓ comparing energy SOURCES fairly (default; AR5)
   USE COMBUSTION-ONLY only when:
     ✓ a regulation specifically demands stack emissions — and label it

   HONEST LIMITS:
     ✗ Marginal is model-dependent and harder to measure than average.
     ✗ Market-based (RECs) can mask a dirty physical grid — disclose location-based too.
     ✗ Factor sets differ (AR5 vs AR6, national inventories) — cite your source and year.
     ✗ 'Other/unknown' fuels fall back to ~230, an approximation.
     ✗ No single number is 'the' truth — report the basis and a sensitivity range.

  • An emission factor = gCO₂ per kWh for a source; carbon = kWh × EF.
  • Use lifecycle (AR5) factors to compare sources fairly; combustion-only wrongly zeroes renewables.
  • Average vs marginal is the biggest nuance: average = whole mix; marginal = the plant your decision actually moves (often gas). It can flip the conclusion (worked ~4.5× flip).
  • Scope 2 must be reported location-based (physical grid) and market-based (contracts/RECs).
  • Data: EIA / ISO for average, Electricity Maps / WattTime for marginal, IPCC AR5 for factors.
  • Intellectual honesty: report the transparent average, disclose the marginal caveat, label every number's basis, show a sensitivity range.

Related: Why Grid Carbon Intensity Varies Hour to Hour · Measuring the Energy & Carbon of AI (the A/E Metric) · Backtesting, Baselines & Sensitivity Analysis · Weighted Averages & Aggregation

Resources