EC2's Instance Lifecycle Model
An EC2 instance is not a thing that is simply on or off. It is a small state machine, and every SDK call you make moves it from one state to another - or deliberately does not.
Search across all documentation pages
An EC2 instance is not a thing that is simply on or off. It is a small state machine, and every SDK call you make moves it from one state to another - or deliberately does not.
Understanding that state machine is what separates confident EC2 automation from scripts that leak money and lose data. This page maps the states, the billing boundary at each one, and what each operation actually changes.
pending, running, stopping, stopped, shutting-down, terminated - and SDK operations are the transitions between them.running state. You pay for compute while running, pay only for storage while stopped, and pay nothing for a terminated instance.Every instance carries a state you can read at any time. The DescribeInstances operation returns State.Name, one of the values below.
| State | Meaning | Billed for compute? |
|---|---|---|
pending | Booting after run or start | No (transitional) |
running | Live and usable | Yes |
stopping | Shutting down toward stopped | No (transitional) |
stopped | Halted, root volume retained | No (storage only) |
shutting-down | Being terminated | No (transitional) |
terminated | Gone, cannot be restarted | No |
The transitional states (pending, stopping, shutting-down) are brief. Your automation mostly cares about the three stable states: running, stopped, and terminated.
Reading state in both SDKs looks like this.
# --- Python (boto3) ---
import boto3
ec2 = boto3.client("ec2", region_name="us-east-1")
resp = ec2.describe_instances(InstanceIds=["i-0abc123"])
state = resp["Reservations"][0]["Instances"][0]["State"]["Name"]
print(state) # e.g. "running"// --- TypeScript (AWS SDK v3) ---
import { EC2Client, DescribeInstancesCommand } from "@aws-sdk/client-ec2";
const ec2 = new EC2Client({ region: "us-east-1" });
const resp = await ec2.send(new DescribeInstancesCommand({ InstanceIds: ["i-0abc123"] }));
const state = resp.Reservations?.[0].Instances?.[0].State?.Name;
console.log(state); // e.g. "running"Each SDK operation is one specific transition. Knowing which state an operation requires as input, and which it produces, prevents most lifecycle bugs.
RunInstances creates a brand new instance from an AMI. It goes pending then running. This is the only operation that produces a new instance id; the others act on one that already exists.
StartInstances takes a stopped instance back to running (through pending). It fails on an instance that is already running or terminated.
StopInstances takes a running instance to stopped (through stopping). The instance halts like a laptop lid closing: the root EBS volume is preserved, but anything in memory and anything on instance store (ephemeral) volumes is lost.
RebootInstances is the odd one out. It does not change state - the instance stays running throughout - and it keeps the same public IP, private IP, and instance store data. It is closer to an operating-system reboot than to a stop/start cycle.
TerminateInstances takes the instance to shutting-down then terminated. This is final. By default the root volume is deleted (governed by DeleteOnTermination), and the instance id is retired forever.
Because start and stop take real time, both SDKs ship waiters so you do not hand-roll polling loops.
# --- Python (boto3) ---
import boto3
ec2 = boto3.client("ec2", region_name="us-east-1")
ec2.stop_instances(InstanceIds=["i-0abc123"])
ec2.get_waiter("instance_stopped").wait(InstanceIds=["i-0abc123"])
print("now stopped")// --- TypeScript (AWS SDK v3) ---
import { EC2Client, StopInstancesCommand, waitUntilInstanceStopped } from "@aws-sdk/client-ec2";
const ec2 = new EC2Client({ region: "us-east-1" });
await ec2.send(new StopInstancesCommand({ InstanceIds: ["i-0abc123"] }));
await waitUntilInstanceStopped({ client: ec2, maxWaitTime: 300 }, { InstanceIds: ["i-0abc123"] });
console.log("now stopped");The billing boundary is the single most important thing to internalize. Compute charges accrue only in the running state, billed per second for most Linux instances (with a one-minute minimum). Stop an idle instance and the compute meter stops immediately.
But stopping is not free. The EBS root volume persists while stopped, and EBS storage bills by the gigabyte-month whether the instance runs or not. A fleet of "stopped to save money" instances with large volumes can still generate a real bill. An Elastic IP that is allocated but not attached to a running instance also bills while stopped.
Stop/start changes several ephemeral attributes. The instance usually moves to different underlying hardware, so the public IPv4 address changes unless you attached an Elastic IP. The private IP inside the VPC is retained. Instance store volumes are wiped. If any of your automation hardcodes a public IP, a stop/start cycle will break it.
Two variants are worth knowing. Hibernation is a stop that also persists RAM to the root volume, so the instance resumes where it left off - useful for long-warm-up workloads, but it requires opt-in at launch and a large enough encrypted root volume. Spot interruption is an involuntary transition: AWS reclaims the capacity and stops or terminates the instance with a two-minute warning, covered in its own page.
The practical rule: model your automation as explicit transitions with waiters, tag instances so you can find and clean them up, and never assume an operation is instantaneous or that ephemeral attributes survive a stop.
shutting-down begins the instance is gone, and the root volume is deleted unless you changed the default.RunInstances creates a new instance; StartInstances resumes an existing stopped one.pending, running, stopping, stopped, shutting-down, and terminated. The first, third, and fifth are transitional; the stable states are running, stopped, and terminated.
For compute, only while it is running, billed per second (one-minute minimum) for most Linux instances. Stopped instances bill only for attached EBS storage and any allocated Elastic IP.
Data on the EBS root and data volumes persists. Data on instance store (ephemeral) volumes and anything in memory is lost when you stop.
Reboot keeps the instance running with the same IPs and instance store intact. Stop/start halts the instance, may move it to new hardware, changes the public IP, and wipes instance store.
No. Termination is final and the instance id is retired. If you need to recover, you must launch a new instance, ideally from an AMI or snapshot you took beforehand.
Because a stop/start cycle assigns a new public IPv4 address. Attach an Elastic IP if you need a stable address across stops.
RunInstances. StartInstances only resumes an existing stopped instance; it never creates one.
Use a waiter: get_waiter("instance_stopped") / instance_running in boto3, or waitUntilInstanceStopped / waitUntilInstanceRunning in SDK v3, instead of polling manually.
A stop variant that also writes RAM to the encrypted root volume so the instance resumes its previous in-memory state on start. It must be enabled at launch.
The root volume is deleted by default (DeleteOnTermination is true for the root). Additional attached volumes follow their own DeleteOnTermination setting.
pending-to-running boot.Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 23, 2026