Tutorial · Aerospace · Physics · JavaScript
📅 July 2026 ⏱ ≈ 25 min 🎯 Intermediate

A Two-Stage Rocket Launch in About 100 Lines of JavaScript

No physics engine, no orbital-mechanics library — just the Tsiolkovsky rocket equation, a gravity-turn pitch program, and a stage separation event, all in a plain requestAnimationFrame loop. By the end you'll have a real (if simplified) staged launch that pitches over, drops its first stage, and coasts toward orbital velocity.

1. Rocket State and Constants

We model the rocket as a point mass with position (downrange distance, altitude), velocity, and pitch angle. Each stage carries its own dry mass, propellant mass, thrust, and specific impulse (Isp) — the two-stage design mirrors real vehicles like Falcon 9, where a lighter, more efficient second stage takes over once the heavy first stage is spent.

const G0 = 9.81;  // m/s^2, standard gravity for Isp conversion

const stage1 = { dryMass: 25000, propMass: 395000, thrust: 7600000, isp: 282 };
const stage2 = { dryMass: 4000,  propMass: 92000,  thrust: 934000,  isp: 348 };
const payload = 8000; // kg

let rocket = {
  x: 0, y: 0,           // downrange (m), altitude (m)
  vx: 0, vy: 0,         // velocity components (m/s)
  pitch: Math.PI / 2,     // radians, pi/2 = straight up
  stage: 1,
  mass: stage1.dryMass + stage1.propMass + stage2.dryMass + stage2.propMass + payload,
  propRemaining: stage1.propMass,
  t: 0,
};
Real-world numbers: the constants above are in the ballpark of a Falcon 9-class vehicle — first stage Isp around 282 s (sea-level Merlin), second stage Isp around 348 s (vacuum-optimized engine, higher efficiency with nothing to push against).

2. Thrust and Mass Flow

Thrust and propellant consumption are linked by the exhaust velocity: F = ṁ · v_e, where v_e = Isp · g₀. As propellant burns, total mass drops every timestep — this shrinking mass is exactly what makes the Tsiolkovsky equation nonlinear and rocket staging worthwhile.

Δv = v_e · ln(m_initial / m_final) = Isp · g₀ · ln(m₀/m₁)
function exhaustVelocity(isp) {
  return isp * G0;
}

function massFlowRate(thrust, isp) {
  return thrust / exhaustVelocity(isp); // kg/s
}

function currentStageConfig(rocket) {
  return rocket.stage === 1 ? stage1 : stage2;
}

3. Gravity Turn Pitch Program

A real rocket doesn't fly straight up the whole way — that would waste enormous amounts of propellant fighting gravity instead of building horizontal (orbital) velocity. A gravity turn starts nearly vertical, then pitches gradually toward horizontal as altitude and speed increase, letting gravity itself do most of the turning.

// Simple empirical pitch program: vertical below 1 km, then a smooth pitchover
function pitchProgram(altitude, t) {
  if (altitude < 1000) return Math.PI / 2; // straight up off the pad
  const turnProgress = Math.min(1, (t - 10) / 120); // pitch over across ~2 minutes
  const targetPitch = (Math.PI / 2) * (1 - turnProgress) + 0.12 * turnProgress;
  return Math.max(0.05, targetPitch);
}
Simplification note: real guidance computers solve a closed-loop optimal-control problem (minimizing propellant for a target orbit); this open-loop schedule is a simplification that still produces a recognizable, physically sound ascent profile.

4. Equations of Motion

Each timestep sums three forces — thrust along the current pitch direction, gravity straight down, and a simplified drag term — then integrates acceleration into velocity and position.

function step(dt) {
  const cfg = currentStageConfig(rocket);
  const mdot = massFlowRate(cfg.thrust, cfg.isp);
  const thrusting = rocket.propRemaining > 0;
  const thrust = thrusting ? cfg.thrust : 0;

  rocket.pitch = pitchProgram(rocket.y, rocket.t);

  const Fx = thrust * Math.cos(rocket.pitch);
  const Fy = thrust * Math.sin(rocket.pitch);

  const rho = 1.225 * Math.exp(-rocket.y / 8500); // simple exponential atmosphere
  const speed = Math.hypot(rocket.vx, rocket.vy);
  const dragMag = 0.5 * rho * speed * speed * 0.3 * 10; // Cd*A ≈ 3 m^2
  const dragX = speed > 0 ? -dragMag * rocket.vx / speed : 0;
  const dragY = speed > 0 ? -dragMag * rocket.vy / speed : 0;

  const ax = (Fx + dragX) / rocket.mass;
  const ay = (Fy + dragY) / rocket.mass - G0;

  rocket.vx += ax * dt;
  rocket.vy += ay * dt;
  rocket.x  += rocket.vx * dt;
  rocket.y  = Math.max(0, rocket.y + rocket.vy * dt);

  if (thrusting) {
    const burned = mdot * dt;
    rocket.propRemaining -= burned;
    rocket.mass -= burned;
  }
  rocket.t += dt;
  checkStaging();
}

5. Stage Separation

When the first stage's propellant runs out, we drop its dry mass entirely — it's dead weight from here on — and switch bookkeeping to the second stage's thrust, Isp, and propellant supply. This discontinuous mass drop is exactly why staging beats a single "average" engine: the vehicle throws away its now-useless empty tanks and structure instead of dragging them to orbit.

function checkStaging() {
  if (rocket.stage === 1 && rocket.propRemaining <= 0) {
    rocket.mass -= stage1.dryMass; // jettison empty first stage
    rocket.stage = 2;
    rocket.propRemaining = stage2.propMass;
    console.log(`Stage separation at t=${rocket.t.toFixed(1)}s, alt=${(rocket.y/1000).toFixed(1)}km`);
  }
}
Why staging works: per the rocket equation, Δv depends on ln(m₀/m₁). A single-stage vehicle must carry its entire empty structure all the way to orbital velocity; a two-stage vehicle throws away stage 1's dead weight partway through, so the same total propellant yields a much higher combined Δv.

6. Trajectory Plotting

A simple canvas plot of downrange distance versus altitude reveals the classic rocket-launch arc: nearly vertical climb, a curving gravity turn, then a long, shallow trajectory as horizontal velocity builds toward orbital speed (~7.8 km/s for a circular LEO orbit).

function renderTrajectory(ctx, trace) {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.beginPath();
  const scaleX = ctx.canvas.width / 500000;   // 500 km downrange fits canvas width
  const scaleY = ctx.canvas.height / 150000;  // 150 km altitude fits canvas height
  trace.forEach((p, i) => {
    const px = p.x * scaleX;
    const py = ctx.canvas.height - p.y * scaleY;
    i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
  });
  ctx.strokeStyle = '#3b82f6';
  ctx.lineWidth = 2;
  ctx.stroke();
}

7. Wiring It Together — the Main Loop

The full simulation runs the physics step several times per rendered frame for numerical stability, records a trajectory trace, and redraws the plot — a complete staged rocket launch in under 100 lines including setup, staging logic, and rendering.

const canvas = document.getElementById('rocket');
const ctx = canvas.getContext('2d');
const trace = [];

function loop() {
  for (let i = 0; i < 20; i++) step(0.05); // 20 sub-steps per frame, dt=0.05s
  trace.push({ x: rocket.x, y: rocket.y });
  renderTrajectory(ctx, trace);
  if (rocket.y >= 0 && rocket.t < 600) requestAnimationFrame(loop);
}
loop();
Reading the result: around t≈150s the trajectory shows a visible kink where stage separation occurs — altitude gain barely pauses because the lighter, more efficient stage 2 keeps accelerating almost immediately. For the underlying rocket-equation math behind why staging multiplies achievable Δv, see Tsiolkovsky Equation and the Limits of Rocketry. For how the same delta-v converts into an actual orbit once thrust ends, see Hohmann Transfer Orbit.
▶ Live Demo

Frequently Asked Questions

What will I learn in this tutorial?

Simulate a staged rocket launch from scratch in about 100 lines of JavaScript: Tsiolkovsky mass ratios, gravity turn, stage separation, and a canvas trajectory plot.

What topics are covered in this tutorial?

This tutorial covers: Rocket State and Constants, Thrust and Mass Flow, Gravity Turn Pitch Program, Equations of Motion, Stage Separation, Trajectory Plotting, Wiring It Together — the Main Loop.

How long does this tutorial take?

This tutorial takes approximately 25 minutes to complete.

What prerequisites do I need before starting?

This is a Intermediate-level tutorial — no special preparation beyond basic JavaScript is assumed.