Same Code, Less Carbon: A Serverless Take on Time Shifting
On a single day in Germany (20 March 2026*), the production of one kilowatt-hour of electricity emitted 231 grams of CO₂ equivalent. Just a few hours later, that same kilowatt-hour emitted 610 grams, that's more than 2.5 times as much.
Same compute, same hardware, same code. Only the time changed.
If your batch jobs run on a fixed schedule, you have an easy way to reduce their carbon footprint. I'll show you a proof of concept demonstrating how little code it actually takes to fix that.
This blog post is based on a talk I gave at various locations. The code is open source and available at github.com/WtfJoke/carbon-aware-serverless-jobs
Why Does the Carbon Intensity of Electricity Change?
The electricity grid is a constantly shifting mix of sources: solar, wind, coal, gas, nuclear, hydro. When the sun is shining and the wind is blowing, the grid runs cleaner. When demand spikes on a still, overcast night, fossil fuels need to supply the energy and the energy mix is dirtier.
This is called carbon intensity, the grams of CO₂ emitted per kilowatt-hour of electricity consumed. It varies not just by country or region, but by time. You can see it live at Electricity Maps. Electricity Maps together with WattTime are the two biggest providers, but there are also smaller ones like EnergyCharts (provided by Fraunhofer Institute for a limited set of countries such as Germany). The advantage of EnergyCharts is that they provide data free of charge, which also includes the forecast data. With Electricity Maps, you can view current (and past) intensity for free, but forecast data requires a paid plan.
Time Shifting: Work Smarter, Not Less
The concept is simple: instead of running a workload at a fixed moment, you give it a window of flexibility: "run this job anytime in the next four hours", and let the system pick the greenest slot within that window.
This is called time shifting, and it's one of many patterns of Green Software Foundation's approach to sustainable development. The beauty of it: you're not reducing the work, not degrading quality, not compromising on results. You're just being smarter about when energy is used.
The pattern works best for workloads that are:
- Flexible in timing - nightly reports, weekly exports, ML training runs
- Not user-facing - nothing a human is actively waiting for
- Compute-intensive - the more energy a job uses, the bigger the potential impact
The Carbon-Aware Computing API
To shift to a greener window, you first need to know which window is greener. That's where carbon-aware-computing.com comes in, a free API (based on the data of EnergyCharts) that provides carbon intensity forecasts for multiple regions. It's based on a fork of carbon-aware-sdk.
You give it the location (e.g. de for Germany) and up to three optional parameters:
- dataStartAt (the earliest time you're willing to start, defaults to now if not set)
- dataEndAt (the latest time you're willing to start, defaults to the newest forecast data available)
- windowSize (the estimated duration (in minutes) of your workload, defaults to 5 minutes. We omit this in the code since the default is fine for us)
The API returns the optimal timestamp to start your workload, which is the slot with the lowest forecasted carbon intensity within your time window.
No infrastructure needed, just a simple HTTP GET call.
The Sample Architecture on AWS
The pattern is platform-agnostic, but here's a serverless implementation on AWS.
I chose Step Functions (state machines) because the service has everything we need included out of the box and we don't need to rely on any additional service inside the state machine: namely a Wait workflow state that can pause until an arbitrary timestamp (the wait time is also not charged), make HTTP calls and invoke any arbitrary service by using AWS SDK cßalls. We don't even need a single Lambda function at all (of course you can do the very same in a Lambda function or on any Function as a Service (FaaS) implementation on other platforms).
This is a proof of concept. The batch job you see is a placeholder. But the pattern can be used like that and is ready to adapt to your own workloads.
The flow works like this:
- EventBridge Scheduler fires at your earliest acceptable start time (e.g., "no earlier than 13:00")
- A Step Functions state machine starts and:
- Calls the carbon-aware-computing.com API to find the optimal time within your window
- Waits until that timestamp
- Runs your actual batch job
You can see that when you pair Step Functions' native Wait state with an HTTP call to the carbon API, you get time shifting without any servers to manage.
Defining the Time Window
The state machine takes a simple input:
1interface CarbonAwareTimeWindowPayload {
2 location: string; // e.g., "de", "fr", "at"
3 earliestDateTime?: string; // ISO 8601 timestamp
4 latestStartInMinutes?: number; // width of the flexibility window (e.g., 120 for 2 hours)
5}
In practice, you'd configure it like this when wiring up your EventBridge schedule:
1const payload: CarbonAwareTimeWindowPayload = {
2 location: "de",
3 // The time you specified for the schedule to invoke its target, in ISO 8601 format
4 earliestDateTime: "<aws.scheduler.scheduled-time>",
5 latestStartInMinutes: 240, // up to 4 hours of flexibility
6};
The <aws.scheduler.scheduled-time> is an EventBridge context attribute, it injects the scheduler's trigger time automatically.
The Step Function: Fetch, Wait, Execute
The state machine chains three steps together. First, it calls the carbon API and stores the optimal timestamp in a variable:
1const earliestDateTimeExpression =
2 "$exists($states.input.earliestDateTime) ? $states.input.earliestDateTime : $now()";
3const latestStartInMillisecondsExpression =
4 "$exists($states.input.latestStartInMinutes) ? $states.input.latestStartInMinutes * 60 * 1000 : 24 * 60 * 60 * 1000";
5
6const fetchBestTimeWindow = HttpInvoke.jsonata(this, "Fetch Best Time Window", {
7 connection,
8 apiRoot: "https://forecast.carbon-aware-computing.com",
9 apiEndpoint: TaskInput.fromText("emissions/forecasts/current"),
10 method: TaskInput.fromText("GET"),
11 queryStringParameters: TaskInput.fromObject({
12 location: "{% $states.input.location %}",
13 dataStartAt: `{% ${earliestDateTimeExpression} %}`,
14 dataEndAt: `{% $fromMillis($toMillis(${earliestDateTimeExpression}) + (${latestStartInMillisecondsExpression})) %}`,
15 }),
16 assign: {
17 optimalTimeStamp:
18 "{% $states.result.ResponseBody[0].optimalDataPoints[0].timestamp %}",
19 },
20});
A few things to notice:
- The
connectionis an EventBridge Connection. It's a way to make HTTP calls without needing any runtime code. In our case, we call the carbon-aware-computing.com API with the API Key (which you can get for free). - The JSONata expressions include fallbacks, if
earliestDateTimeisn't set, it defaults to now; iflatestStartInMinutesis missing, it defaults to a 24-hour window. - Some JSONata calculations to convert the
latestStartInMinutesinto milliseconds to calculate thedataEndAtparameter, which is the latest acceptable start time for the workload.
Then it waits until that moment:
1const waitStep = Wait.jsonata(this, "Wait for time Window", {
2 time: WaitTime.timestamp("{% $optimalTimeStamp %}"),
3});
Finally, it chains everything together with your actual job:
1fetchBestTimeWindow.next(waitStep).next(yourBatchJobTask);
The yourBatchJobTask is whatever you need - a Lambda invocation, an ECS task, a Glue job. The created CDK Construct is completely agnostic to what the work actually is. You wrap your existing job, not rewrite it.
Try It Yourself
The full implementation is open source: github.com/WtfJoke/carbon-aware-serverless-jobs
To deploy it yourself:
- Get a free API key at carbon-aware-computing.com
- Store it in AWS Secrets Manager under
/carbon-aware-computing/api-key - Clone the repository, run
npm install && npm run cdk deploy - Trigger the
CarbonAwareServerlessBatchJobsSchedulerstate machine
Then swap the placeholder task for your real workload, tune the flexibility window, and you're shifting.
The Bottom Line
Going green doesn't always mean doing less. Sometimes it means doing the same thing, just a little later.
Time shifting isn't tied to AWS either. The carbon-aware-computing.com API works from any runtime, in any language, on any platform.
Time shifting is one entry point into sustainable software engineering. Just awareness of when your code runs, and a nudge toward the greener moment.
The electricity grid is already telling you when it's cleanest. Your batch jobs might as well listen.
* Measurements from Electricity Maps for Germany in a 24 hour time window between March 19–20, 2026.
Blog author
Manuel
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.