← Back to Learning Hub

Green / Carbon-Aware Systems — Interview Questions

Carbon-aware schedulingTradeoffsAdvanced13 min

By: Anacodic Team

A question bank for green-computing, carbon-aware scheduling, and grid-emissions interviews. Each question has a thorough, interview-grade answer you can say out loud. Companies with real programs here: Google (carbon-intelligent compute), Microsoft (Carbon Aware SDK), AWS (sustainability pillar), Meta (net-zero datacenters). Deep dives: Carbon-Aware Scheduling of Flexible Loads · Green Computing Tradeoffs (No Free Lever).


Q1. How would you make a datacenter carbon-aware?

Answer. Four layers, from data to decision:

  1. Signal. Get an hourly (or finer) grid carbon-intensity feed for each region you operate in — from the EIA Open Data API (fuel mix → compute CI(t) yourself) or a provider like Electricity Maps / WattTime (they hand you average and marginal intensity directly).
  2. Classify workloads. Split jobs into latency-critical (serve now — user requests, databases) and deferrable/flexible (batch ML training, data pipelines, reindexing, backups). Only the deferrable ones can be shifted.
  3. Shift. For each deferrable job with a deadline, run a carbon-aware scheduler: pick the feasible start window that minimizes Σ P·CI(s+h), subject to a per-hour power cap M. Shift in time (run at 2am when it's windy) and, if data allows, in space (run in a region whose grid is cleaner right now).
  4. Prove & monitor. Backtest greedy vs a carbon-blind FIFO baseline on historical data to size the win before rollout, then monitor live gCO₂ saved, report on an average basis, and disclose the marginal caveat.

This is exactly Google's carbon-intelligent compute management: they forecast day-ahead carbon intensity and shift flexible compute (like some ML training) into greener hours. Microsoft ships the open-source Carbon Aware SDK so any app can query "how clean is the grid right now?" and defer work. Name-drop these — they show you know it's a solved, deployed pattern, not a thought experiment.


Q2. What is grid carbon intensity and how do you get it?

Answer. Carbon intensity is how many grams of CO₂ each kWh of grid electricity carries, right now:

   CI(t) = Σ_f gen_f(t)·EF_f / Σ_f gen_f(t)     [ gCO2/kWh ]

It is the generation-weighted average of each fuel's emission factor EF_f. Using IPCC AR5 lifecycle factors (gCO₂/kWh): coal 820, oil 650, gas 490, other ≈230, solar 48, hydro 24, nuclear 12, wind 11. Clean fuels pull CI down; fossil pulls it up.

How you get it: pull the hourly fuel mix from the EIA Open Data API (ISO-NE / PJM / MISO / CAISO / NYISO / ERCOT / SPP), multiply each fuel's MW by its factor, and divide by total generation. Or skip the arithmetic and call Electricity Maps or WattTime, which serve it directly.

Why it matters: CI(t) swings a lot — on ISO New England a windy overnight is around 59 gCO₂/kWh while a still evening peak is around 350 — roughly a 6× difference for the same kilowatt-hour. That swing is the entire opportunity for carbon-aware scheduling.


Q3. Average vs marginal emissions — why does it matter?

Answer. They answer different questions:

  • Average CI = the mean intensity across all generation this hour. It answers "how dirty is the grid overall?" — the right basis for reporting and for a transparent backtest.
  • Marginal CI = the intensity of the specific plant that ramps to serve one more unit of demand (often gas on many grids). It answers "what did my decision actually cause?" — the right basis for attribution.

It matters because when you move a job, you don't shift the average generator — you shift the marginal one. So a saving that looks large on an average basis can look different (often smaller) on a marginal basis. The honest move in an interview: report average, disclose the marginal caveat, and name Electricity Maps / WattTime as the marginal-signal sources. Volunteering this distinction unprompted is a strong senior signal — it shows you know the headline number has an asterisk.


Q4. What's the difference between CCI (measuring an AI task's carbon) and carbon-aware scheduling (grid CI + timing)?

Answer. They are complementary, not the same thing:

  • CCI (Carbon Cost of Intelligence) — published, DOI 10.3390/en19030642 — measures how much energy/carbon one AI task costs. Its A/E metric is the job's own footprint.
  • Carbon-aware scheduling (this module) measures how dirty the grid is each hour, CI(t), and shifts deferrable jobs into clean windows.

Analogy: CCI = how much fuel a car burns per trip; carbon-aware scheduling = knowing when the electricity itself is cleanest and running then. One sizes the footprint of the work; the other picks the best hour to incur it. They compose cleanly: you could take a CCI-measured AI job and hand it to this scheduler to run it in the greenest window. If an interviewer conflates the two, drawing this distinction crisply is a differentiator.


Q5. How do you prove carbon savings WITHOUT deploying anything?

Answer. A historical backtest — no live system, no forecasting:

  1. Pull real past hourly fuel-mix data (EIA) and compute the CI(t) curve.
  2. Define your deferrable jobs (P, D, release, deadline).
  3. Run two schedulers on the same jobs + same curve: the greedy carbon-aware one (min-cost feasible window) and a FIFO baseline (earliest feasible start — the carbon-blind default).
  4. Sum emissions and report:
   Savings% = (Total_FIFO − Total_greedy) / Total_FIFO × 100

Because it replays history, a backtest needs zero forecasting and zero deployment — it is fully standalone, which is exactly why this work can be built and published on its own. FIFO is the right baseline because it isolates the value of timing alone: same jobs, same energy P·D, only the window differs. (Scheduling into the future needs some CI forecast — but a simple one or day-ahead published values suffice, still not a full demand-forecasting system.)


Q6. When does carbon-aware scheduling NOT help?

Answer. Savings are the product of slack and CI variance, so they collapse when either is near zero:

  • No slack. slack = deadline − release − duration. If the job must run now (latency-critical, tight SLA), there is exactly one feasible window → 0% savings, no matter how variable the grid.
  • Flat CI curve. A single-fuel / baseload grid (say heavy coal or heavy nuclear all day) has no swing → every window is equally dirty → nothing to shift into.
  • Tight capacity. A hard per-hour cap M blocks jobs from the greenest hours, shrinking the win (though rarely to zero).
  • High freshness cost. If deferring makes the result stale enough to be useless, the "saving" isn't worth it.

The professional answer: don't assert it helps — measure it. Run a slack/capacity sensitivity sweep on a backtest; if the curve is flat or near-zero at the workload's real slack and M, say so and move on. See Green Computing Tradeoffs (No Free Lever).


Q7. Temporal vs spatial load shifting — what's the difference and when do you pick each?

Answer. Two independent axes for moving a flexible load away from dirty power:

  • Temporal shifting — move the job in time: run the batch at 3am (windy, clean) instead of 8pm (peak, dirty). No data movement, simplest to deploy. Bounded by the job's slack.
  • Spatial shifting — move the job in space: run it in a region/datacenter whose grid is cleaner right now ("follow the renewables"). Bounded by where the data lives and by egress/latency/locality costs.

Pick temporal first — it's cheap and needs no data movement. Pick spatial when a remote grid is substantially cleaner and the data is movable (or already replicated) and latency/compliance allow it. Large cloud providers (Google, Microsoft, AWS, Meta) do both, often together: shift in time within a region and across regions when the gap is big enough to justify moving the work.


Q8. How would you schedule deferrable jobs under a per-hour capacity limit?

Answer. Without a shared limit, each job is independent and greedy is provably optimal (exchange argument: swap any job into a cheaper feasible window and the total strictly drops). A per-hour capacity cap M breaks that — jobs contend:

   constraint:  Σ_{jobs running in hour h} P_j ≤ M     for every hour h

Now use ascending-slack greedy (a real heuristic, not a closed form):

  1. Sort jobs by slack ascending — tightest deadline first (it has the least room to move).
  2. For each job, consider only windows where every hour still has remaining_capacity ≥ P_j.
  3. Pick the cleanest feasible such window; decrement each of those hours' remaining capacity by P_j.
  4. Continue. Later, roomier jobs absorb the displacement — some get pushed to dirtier hours.

Tightest-slack-first gives the least-flexible jobs first pick of the clean hours, which is the sensible priority. The tighter M, the more jobs spill into dirty windows and the lower the fleet savings — that contention is the core capacity tradeoff.


Q9. How do you handle the carbon-intensity forecast being wrong?

Answer. Backtests use perfect historical CI, but scheduling forward needs a forecast, and forecasts err. Defenses:

  • Prefer day-ahead published values where the ISO provides them, or a simple forecast (e.g. same-hour-yesterday, or a small model on weather + load) — you don't need a heavyweight system to beat carbon-blind FIFO.
  • Re-plan on a rolling horizon. Re-optimize each hour as fresh data lands, so an early bad forecast gets corrected before the job actually runs.
  • Keep a deadline safety margin. Never let carbon optimization risk a missed deadline — the deadline is a hard constraint; carbon is the objective.
  • Bound the downside. Even a wrong forecast rarely does worse than FIFO by much, because any move toward a historically-clean window still tends to help. Report expected savings with a sensitivity band, not a single number.

Crucially, forecast error is a future-scheduling concern only — it does not touch the standalone historical backtest, which is exact.


Q10. How is a lifecycle emission factor different from a combustion-only one?

Answer. An emission factor EF_f is gCO₂ per kWh for fuel f, but what you count differs:

  • Combustion-only (operational) — just the CO₂ from burning the fuel at the plant. For wind, solar, nuclear, hydro this is basically zero (nothing is combusted).
  • Lifecycle (LCA) — the full chain: building the plant, manufacturing panels/turbines, mining/refining/transporting fuel, and decommissioning, amortized per kWh. This is why the IPCC AR5 factors we use give wind 11, solar 48, nuclear 12, hydro 24 — small but non-zero, because building the hardware has embodied carbon even though running it emits nothing.

Using lifecycle factors is the honest, apples-to-apples basis — it prevents overstating renewables as perfectly clean and lets you compare, say, solar (48) vs gas (490) on the same footing. Always state which basis you're on; mixing combustion-only fossil factors with lifecycle renewable factors is a classic accounting error.


Q11. Coding — return the minimum-carbon start window for a job.

Answer. Given the CI array and a job (P, D, release, deadline), scan feasible starts and pick the cheapest window:

def best_start(ci, P, D, release, deadline):
    """Feasible start s in [release, deadline-D] minimizing sum_{h<D} P*ci[s+h]."""
    return min(range(release, deadline - D + 1),
               key=lambda s: sum(P * ci[s + h] for h in range(D)))
  • Complexity: naive is O(W·D) where W is the number of feasible starts. Optimize with a prefix-sum of ci so each window cost is O(1), giving O(W) total:
def best_start_fast(ci, P, D, release, deadline):
    pre = [0]
    for c in ci:
        pre.append(pre[-1] + c)                      # prefix sums
    def cost(s):
        return P * (pre[s + D] - pre[s])             # O(1) window sum
    return min(range(release, deadline - D + 1), key=cost)

Follow-ups an interviewer will push on: it's a fixed-width sliding window (sliding-window-minimum territory); with a capacity cap it's no longer independent per job (ascending-slack greedy heuristic, Q8); the energy P·D is invariant so only the CI terms drive the choice.


Q12. Coding — compute the savings percentage vs a FIFO baseline.

Answer. Run both schedulers on the same jobs and curve, then take the relative reduction:

def window_cost(ci, P, D, s):
    return sum(P * ci[s + h] for h in range(D))

def savings_percent(ci, jobs):
    """jobs: list of (P, D, release, deadline). Greedy vs FIFO (earliest start)."""
    total_fifo = total_greedy = 0.0
    for (P, D, release, deadline) in jobs:
        total_fifo += window_cost(ci, P, D, release)               # FIFO = earliest start
        best = min(window_cost(ci, P, D, s)
                   for s in range(release, deadline - D + 1))      # greedy min-cost window
        total_greedy += best
    return (total_fifo - total_greedy) / total_fifo * 100.0

# Worked EV: CI overnight curve, one 7 kW / 3 h job due 13 hours out.
CI = [350, 330, 300, 250, 200, 150, 100, 80, 60, 59, 62, 90, 150]
print(round(savings_percent(CI, [(7, 3, 0, 13)]), 1))   # -> 81.5

Points to make while coding: FIFO (earliest feasible start) is the carbon-blind baseline that isolates the value of timing; energy P·D is identical for both, so the whole difference is which hour's CI you pay; and the reported number is on an average-CI basis (disclose the marginal caveat). For the single worked EV it returns 81.5% — a windy 2–5am window instead of the 6pm peak.


Resources