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:
- 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). - 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.
- 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 capM. 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). - 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/Emetric 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:
- Pull real past hourly fuel-mix data (EIA) and compute the
CI(t)curve. - Define your deferrable jobs
(P, D, release, deadline). - 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).
- 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
Mblocks 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):
- Sort jobs by slack ascending — tightest deadline first (it has the least room to move).
- For each job, consider only windows where every hour still has
remaining_capacity ≥ P_j. - Pick the cleanest feasible such window; decrement each of those hours' remaining capacity by
P_j. - 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)whereWis the number of feasible starts. Optimize with a prefix-sum ofciso each window cost isO(1), givingO(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
- EIA Open Data API (hourly fuel-type data) — https://www.eia.gov/opendata/
- Electricity Maps (average & marginal CI) — https://www.electricitymaps.com/
- WattTime (marginal emissions) — https://www.watttime.org/
- IPCC AR5 lifecycle emission factors (WG3 Annex III) — https://www.ipcc.ch/report/ar5/wg3/
- Google — Carbon-intelligent compute management — https://blog.google/inside-google/infrastructure/data-centers-work-harder-sun-shines-wind-blows/
- Microsoft — Carbon Aware SDK (Green Software Foundation) — https://github.com/Green-Software-Foundation/carbon-aware-sdk
- AWS — Sustainability Pillar, Well-Architected Framework — https://docs.aws.amazon.com/wellarchitected/latest/sustainability-pillar/
- Deep dives: Carbon-Aware Scheduling of Flexible Loads · Green Computing Tradeoffs (No Free Lever) · Why Grid Carbon Intensity Varies Hour to Hour