Introduction

Try balancing a broomstick upright on your palm. Left alone, it always falls — straight up is the one position gravity fights hardest to ruin. To keep it standing you have to constantly watch it lean and nudge your hand back underneath it, again and again, faster than you can think about it.

That everyday trick is exactly the inverted pendulum: a pole hinged to a cart that can slide left and right, with the pole balanced tip-up instead of hanging down. It is the single most famous example of an unstable equilibrium in engineering — a resting point where the tiniest disturbance grows instead of fading away.

Nobody balances a real pendulum by pre-planning every move in advance. You watch, you react, you correct. Turning that instinct into precise mathematics is the heart of control theory, and the inverted pendulum is its rite of passage.

Catch the Falling Pole

Below is a simulated cart with a pole balanced on top, governed by the real nonlinear equations of motion. Start it upright with a tiny tilt and watch what happens with the controller off versus on.

<p class="hint">A pole is hinged to a cart on a track. With the controller off it just falls. Turn it on and a feedback law reads the pole's tilt and speed (and the cart's position and speed) and pushes the cart to catch it.</p>
<div class="stage">
  <canvas id="scene" width="520" height="260"></canvas>
</div>
<div class="status" id="status">Controller off — the pole will fall on its own.</div>
<div class="btns">
  <button id="toggle" type="button">Start controller</button>
  <button id="nudge" type="button">Nudge</button>
  <button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.stage { background: #eef2f6; border: 1px solid #cdd9e3; border-radius: 10px; margin: .4rem 0; }
canvas { display: block; width: 100%; height: 260px; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
         background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Cart-pole dynamics (nonlinear, standard form): cart mass M, pole mass m,
// half-length l, gravity g. theta is measured from the upright position, so
// theta = 0 means the pole points straight up.
const g = 9.8, M = 1.0, m = 0.1, l = 0.5; // cart mass, pole mass, half-length, gravity
const MAX_FORCE = 40, DT = 0.016;

// State: x/v = cart position/velocity, theta/w = pole angle/angular velocity.
let state = { x: 0, v: 0, theta: 0.12, w: 0 };
let running = false;
let fell = false;

// Hand-tuned state-feedback gains K, verified numerically to stabilize small tilts.
const K = { x: 5, v: 5, theta: 40, w: 10 };

function derivative(s, F) {
  const sinT = Math.sin(s.theta), cosT = Math.cos(s.theta);
  const totalMass = M + m;
  const temp = (F + m * l * s.w * s.w * sinT) / totalMass;
  const thetaAcc = (g * sinT - cosT * temp) / (l * (4 / 3 - m * cosT * cosT / totalMass));
  const xAcc = temp - m * l * thetaAcc * cosT / totalMass;
  return { x: s.v, v: xAcc, theta: s.w, w: thetaAcc };
}

// Control law: force = Kx*x + Kv*v + Ktheta*theta + Kw*w, clamped to +/- MAX_FORCE.
function controlForce(s) {
  const raw = K.x * s.x + K.v * s.v + K.theta * s.theta + K.w * s.w;
  return Math.max(-MAX_FORCE, Math.min(MAX_FORCE, raw));
}

function step() {
  const F = running ? controlForce(state) : 0;
  const d = derivative(state, F);
  state = {
    x: state.x + d.x * DT,
    v: state.v + d.v * DT,
    theta: state.theta + d.theta * DT,
    w: state.w + d.w * DT,
  };
  if (Math.abs(state.x) > 2.2) state.x = Math.sign(state.x) * 2.2;
  if (!fell && Math.abs(state.theta) > Math.PI / 2) {
    fell = true;
    setStatus('✗ The pole fell. Reset and try again.', 'bad');
  }
}

function draw() {
  const canvas = document.getElementById('scene');
  const ctx = canvas.getContext('2d');
  const w = canvas.width, h = canvas.height;
  ctx.clearRect(0, 0, w, h);
  const originX = w / 2, trackY = h - 60;
  const pxPerM = 90;

  // draw the track the cart slides on
  ctx.strokeStyle = '#adb1b8';
  ctx.lineWidth = 3;
  ctx.beginPath();
  ctx.moveTo(40, trackY + 24);
  ctx.lineTo(w - 40, trackY + 24);
  ctx.stroke();

  const cartX = originX + state.x * pxPerM;
  const cartW = 70, cartH = 34;

  // draw the cart body
  ctx.fillStyle = '#1d3557';
  ctx.fillRect(cartX - cartW / 2, trackY - cartH / 2, cartW, cartH);

  // draw the cart's wheels
  ctx.fillStyle = '#222';
  ctx.beginPath(); ctx.arc(cartX - cartW / 3, trackY + cartH / 2, 6, 0, Math.PI * 2); ctx.fill();
  ctx.beginPath(); ctx.arc(cartX + cartW / 3, trackY + cartH / 2, 6, 0, Math.PI * 2); ctx.fill();

  // draw the pole from the pivot to its tip
  const poleLenPx = l * 2 * pxPerM;
  const pivotY = trackY - cartH / 2;
  const tipX = cartX + poleLenPx * Math.sin(state.theta);
  const tipY = pivotY - poleLenPx * Math.cos(state.theta);
  ctx.strokeStyle = fell ? '#c92f3c' : '#e63946';
  ctx.lineWidth = 6;
  ctx.lineCap = 'round';
  ctx.beginPath();
  ctx.moveTo(cartX, pivotY);
  ctx.lineTo(tipX, tipY);
  ctx.stroke();

  // draw the weighted tip of the pole
  ctx.fillStyle = fell ? '#c92f3c' : '#e63946';
  ctx.beginPath(); ctx.arc(tipX, tipY, 8, 0, Math.PI * 2); ctx.fill();

  // draw the hinge connecting pole and cart
  ctx.fillStyle = '#457b9d';
  ctx.beginPath(); ctx.arc(cartX, pivotY, 4, 0, Math.PI * 2); ctx.fill();
}

function setStatus(t, cls) {
  const el = document.getElementById('status');
  el.textContent = t;
  el.className = 'status ' + cls;
}

function loop() {
  if (!fell) step();
  draw();
  requestAnimationFrame(loop);
}

function reset() {
  state = { x: 0, v: 0, theta: 0.12, w: 0 };
  running = false;
  fell = false;
  document.getElementById('toggle').textContent = 'Start controller';
  setStatus('Controller off — the pole will fall on its own.', '');
}

document.getElementById('toggle').addEventListener('click', () => {
  if (fell) return;
  running = !running;
  document.getElementById('toggle').textContent = running ? 'Stop controller' : 'Start controller';
  setStatus(running ? 'Controller on — balancing.' : 'Controller off — the pole will fall on its own.', running ? 'ok' : '');
});
document.getElementById('nudge').addEventListener('click', () => {
  if (fell) return;
  // give the pole a random angular kick to test the controller's recovery
  state.w += (Math.random() < 0.5 ? -1 : 1) * 1.8;
  setStatus(fmt('Nudged! Watch the controller react.', {}), running ? 'ok' : '');
});
document.getElementById('reset').addEventListener('click', reset);

draw();
requestAnimationFrame(loop);
Result
Console

With the controller off, the pole is on its own — gravity wins almost immediately, no matter how small the starting tilt. Turn the controller on and it measures the pole's angle and angular velocity (plus the cart's position and velocity) many times per second and pushes the cart with exactly the force needed to slide back underneath the pole. Try the Nudge button while it's balancing: the controller reacts and recovers, the same way your hand does with the broomstick.

The Real Complexity

The physics is genuinely nonlinear: the cart's mass MM, the pole's mass mm and length ll, gravity gg, and the coupling between the cart's push and the pole's swing all interact through sines and cosines of the angle θ\theta. Near the top, though, θ\theta is small, so sin⁡θ≈θ\sin\theta \approx \theta and cos⁡θ≈1\cos\theta \approx 1 — the system behaves almost like a simple, linear one.

  • Linearize. Around the upright point, the equations reduce to a linear system s˙=As+Bu\dot{s} = As + Bu, where the state s=(x,x˙,θ,θ˙)s = (x, \dot{x}, \theta, \dot\theta) tracks cart position, cart velocity, pole angle and angular velocity, and uu is the force applied to the cart.
  • Check controllability. The system is controllable — a formal, checkable condition (full rank of the Kalman matrix) meaning the cart can, in principle, steer every one of those four quantities wherever it needs to go, including back to zero.
  • Feed back the state. The classic fix is state feedback: choose a force u=−Ksu = -Ks that is a weighted combination of all four measurements. Every 1960s-era control textbook — starting with Rudolf Kálmán's work on controllability and pole placement — shows how to pick the gain vector KK so the closed-loop system decays any small tilt back to upright, instead of amplifying it.
  • It is a solved problem. Unlike an NP-complete puzzle, balancing the cart-pole isn't a search over exponentially many possibilities — it's linear algebra. Once KK is computed (by pole placement or by minimizing an energy-like cost with LQR, the Linear-Quadratic Regulator), stability is guaranteed near the upright point, not just observed in simulation.

The catch is "near." Push the pole too far from vertical and the linear approximation breaks down — which is exactly why the demo's controller can recover from a gentle nudge but not from a pole started on its side.

Where It Matters

"Keep an unstable system near a target state using constant feedback" turns out to be everywhere once you know to look for it:

  • Rocket landings: a returning booster balancing on its engine plume is, dynamically, an inverted pendulum many meters tall — the same state-feedback idea, scaled up enormously.
  • Segways and hoverboards: they stay upright by measuring tilt dozens of times per second and driving the wheels back underneath the rider, exactly like the cart chasing the pole.
  • Humanoid and legged robots: balancing on two feet is a harder, multi-jointed cousin of the same unstable-equilibrium problem.
  • Benchmarking learning algorithms: "CartPole" is one of the first environments any reinforcement-learning method is tested on, precisely because success or failure (the pole falls) is instant and unambiguous.

Master the inverted pendulum and you've met the working core of modern control engineering — the same feedback logic used in flight control, robotics and any system that would rather fall than stand still on its own.

Conclusion

The inverted pendulum looks like a physics-class toy, but it captures the essence of every unstable system worth controlling: left alone it falls, watched and corrected fast enough it stands. Linearizing near the top turns an intimidating nonlinear tangle into ordinary linear algebra, and a single feedback law — computed once, applied forever — is enough to hold the balance.

So the next time a rocket booster settles onto a landing pad or a two-wheeled robot glides past you without toppling, remember: underneath it is the same broomstick-on-a-palm trick, just written down precisely enough for a computer to do it thousands of times per second.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/inverted-pendulum-balance/Content licensed under CC BY-NC 4.0.