When I review motion bugs in games, robotics projects, or data visualizations, a surprisingly large share comes from the same root: treating circular motion like “moving around a circle” instead of “moving while constantly turning the velocity vector.” That difference sounds philosophical, but it shows up as very real symptoms—objects that drift outward, cameras that jitter, or a “perfect orbit” that gains energy every frame.
Circular motion is one of those topics where the physics is clean, but the implementation can get messy if you mix coordinate systems, forget units, or confuse what’s real (forces) with what’s a modeling convenience (fictitious forces in a rotating frame). In this post I’ll walk you through the concepts I actually reach for when I need circular motion to behave correctly: the kinematics (angles and arc length), the dynamics (centripetal force, and what people mean by centrifugal force), the difference between circular and rotational motion, and a few robust coding patterns for simulation.
If you’re here to build something—an animation, a sensor fusion model, a trajectory planner, or a physics engine—you’ll leave with formulas you can trust, plus implementation guidance that prevents the most common mistakes.
Circular motion is “constant turning,” not “constant speed”
If you picture an object moving in a circle of radius r, the first trap is assuming circular motion implies constant speed. It doesn’t. Circular motion only means the path is circular. Speed might be constant (uniform circular motion) or changing (non-uniform circular motion).
The non-negotiable fact is this: even if the speed is constant, the velocity is not constant, because velocity includes direction. A changing velocity implies acceleration. That’s why a car can be “not speeding up” on a circular track and still feel a sideways push—you’re accelerating toward the center.
A clean mental model is to keep three vectors separate:
- Position: r⃗(t) from the center to the object.
- Velocity: v⃗(t), tangent to the circle.
- Acceleration: a⃗(t), which can have a radial component (toward/away from the center) and a tangential component (along the tangent).
For a circle centered at the origin in 2D, a common parameterization is:
- x(t) = r cos θ(t)
- y(t) = r sin θ(t)
That’s already enough to write stable code: if you can compute θ(t) correctly, you can compute position. But to model forces, limits, and real behavior, you need the derivatives:
- θ̇(t) = ω(t) is angular velocity.
- θ̈(t) = α(t) is angular acceleration.
Programmer tip: I treat θ as the “phase” of motion. Phase is usually the best state variable to integrate over time (with care), because it maps directly to position and avoids drift from “keep projecting back onto the circle” hacks.
Angles, radians, and the equations I actually use
Radians are not a “different unit system.” They are the natural way the circle itself measures angles.
Key relationships (for radius r):
- Arc length: s = r θ
- Tangential speed: v = r ω
- Tangential acceleration: a_t = r α
You’ll also see periodic quantities:
- Period: T = time for one revolution
- Frequency: f = 1 / T
- Angular velocity: ω = 2π f = 2π / T
If you only remember one conversion, remember this:
- 360° = 2π radians
From a coding perspective, radians win because every standard math library expects radians (sin, cos), and because derivatives behave cleanly: d/dθ(sin θ) = cos θ only works with radians.
A practical “unit hygiene” rule I follow:
- Store angles in radians.
- Store ω in rad/s and α in rad/s².
- Convert to degrees only at the UI boundary (labels, debug overlays).
A small “traditional vs modern” workflow table
When I’m building something in 2026, I rarely choose between “math” and “code.” I do both, and I cross-check.
Traditional workflow
—
Derive on paper
Inspect equations
Free-body diagram
AI-assisted coding fits here naturally: I often ask an assistant to generate test cases (edge radii, tiny dt, large ω) while I keep ownership of the physics and units.
Uniform circular motion: centripetal acceleration shows up even at constant speed
Uniform circular motion means speed is constant and the radius is constant. The acceleration is entirely radial (toward the center).
The centripetal acceleration magnitude is:
- a_c = v² / r
Using v = r ω, you also get:
- a_c = ω² r
Direction: always toward the center (opposite the radius vector).
A quick derivation intuition I like: as the object moves, its velocity vector rotates. The rate at which it rotates is ω, and the size of v is constant, so the “change in v” per time points inward. That geometric argument leads straight to ω² r.
Why this matters in code
If you animate a point by stepping position forward linearly:
- p_next = p + v * dt
and you merely rotate v a bit each frame, you can end up with small radius drift because your discrete steps don’t preserve the circle.
Two robust options:
1) Parametric update: update θ, then compute (x, y) from sin/cos.
2) Physics update: compute acceleration a⃗ = -(v²/r) r̂ and integrate, but then you must handle numerical error carefully.
For UI/animation where the radius is a constraint (camera orbit, menu carousel), I almost always pick (1). For “physics where constraints can break” (rope tension can snap, traction can fail), I pick (2) and let the radius emerge from forces.
A concrete example: a car on a circular track
Suppose r = 50 m and v = 20 m/s (~72 km/h). Then:
- a_c = v² / r = 400 / 50 = 8 m/s²
That’s close to 0.8 g. You’ll feel that in a turn.
This is also where people misread “centripetal force” as a special new force. It’s not a new kind of force; it’s the name for the net inward force required to produce that inward acceleration.
Non-uniform circular motion: radial + tangential acceleration
Non-uniform circular motion means speed changes while moving on a circular path. Now acceleration has two components:
- Radial (centripetal): a_r = v² / r = ω² r (points inward)
- Tangential: a_t = dv/dt = r α (points along the direction of motion if speeding up)
The total acceleration magnitude is:
-
a⃗ = sqrt(ar² + at²)
(because radial and tangential components are perpendicular).
In practice, this is exactly what you see in many real systems:
- A motor spins up a wheel: tangential acceleration dominates early, then settles.
- A drone yaws while accelerating forward: your world-frame acceleration is a mix.
- A user drags a knob UI faster: α is not zero.
Edge case I watch for: discontinuous α
In code, it’s tempting to do “instant changes”:
- ω jumps from 2 to 10 rad/s at t = 1.0 s.
That implies infinite angular acceleration at the jump (a spike). Sometimes that’s fine for a UI, but it creates unrealistic forces in a physics model, and it can destabilize controllers.
If you need physically plausible motion, I recommend shaping ω with a ramp (limit α) or even a jerk limit (limit dα/dt). You don’t need fancy math to start; a simple clamp on α already makes behavior far less twitchy.
When NOT to model something as perfect circular motion
I avoid “perfect circle” assumptions when:
- The radius is not constrained (orbiting in space without a central force model).
- The system has strong disturbances (wind, collisions).
- You’re near slip limits (tires on wet pavement): the path becomes a spiral or slide.
A circle is a constraint. If your real system doesn’t have that constraint, model the force and let the path be whatever it becomes.
Centripetal force vs centrifugal force (and why people argue about it)
Centripetal force is the net inward force needed for circular motion:
- Fc = m ac = m v² / r = m ω² r
Where does it come from?
- Tension for a stone on a rope.
- Normal force and friction for a tire on a road.
- Gravity for many orbital motions.
- Magnetic force for a charged particle in a magnetic field.
Centrifugal force is different: it’s a fictitious force that appears if you analyze motion in a rotating (non-inertial) frame.
Here’s how I keep it straight:
- In an inertial frame (ground frame), the object accelerates inward. No “outward force” is needed to explain it.
- In a rotating frame that turns with the object, you can treat the object as (locally) at rest, but you must add fictitious forces to make Newton’s laws work in that non-inertial frame. One of those is the centrifugal force, pointing outward with magnitude m ω² r.
Why you still might use the centrifugal idea in software
Even though it’s fictitious, it can be a useful bookkeeping tool when you intentionally work in a rotating coordinate system:
- Simulating a spinning space station’s interior.
- Modeling a turntable, rotating machinery, or a rotating reference frame in robotics.
- Doing vehicle dynamics in a body frame.
If you do that, don’t stop at centrifugal force. Rotating frames typically require:
- Centrifugal: m ω⃗ × (ω⃗ × r⃗)
- Coriolis: 2m (v⃗_rel × ω⃗)
- Euler force (if ω changes): m (r⃗ × dω⃗/dt)
You don’t need all of these for simple 2D constant-ω problems, but you should know they exist so you don’t “mysteriously” miss sideways drift terms in a rotating frame.
Banked curves: friction is optional if geometry helps you
A banked turn tilts the normal force so part of it points inward. That reduces how much friction must supply centripetal force. In implementation terms (vehicle sims, even arcade ones), banking changes the available inward force budget before the tire slips.
Circular motion vs rotational motion (the difference pays off)
People casually say “rotation” and “circular motion” interchangeably, but they’re not the same thing.
- Circular motion: a point mass (or the center of mass of a body) moves along a circular path.
- Rotational motion: a rigid body spins about an axis.
You can have one without the other:
- A satellite in a circular orbit: its center of mass follows a circle (circular motion), but it might not spin.
- A spinning top in place: it rotates, but its center of mass might stay roughly fixed.
You can also have both:
- A wheel rolling: the wheel rotates, and its center of mass translates (often in a line, not a circle).
- A figure skater spinning while moving around the rink.
Practical programming consequences
If you’re animating a rigid body, you usually track:
- Translational state: position p⃗, velocity v⃗
- Rotational state: orientation (angle in 2D, quaternion in 3D), angular velocity ω⃗
A common bug is mixing them: applying angular velocity to position directly, or using a body’s spin as if it were orbital angular velocity.
Another common pitfall is choosing the wrong pivot:
- Rotating around the origin is not the same as rotating around the object’s center.
- In 2D graphics, this is the difference between “orbit” and “spin.”
If you’re building a UI carousel, you want orbit (circular motion around a center). If you’re building a loading spinner icon, you want spin (rotation about the icon’s center). Same trig, different intent.
Circular motion formulas you’ll reuse (and a quick “mistake detector”)
Here’s the short list I keep handy:
Kinematics:
- s = r θ
- v = r ω
- a_t = r α
Uniform circular motion:
- a_c = v² / r = ω² r
- F_c = m v² / r = m ω² r
- ω = 2π / T = 2π f
Non-uniform circular motion:
-
a⃗ = sqrt((v²/r)² + (dv/dt)²)
Mistake detector: dimensional analysis
When I’m unsure, I check units.
- ω must be 1/s (rad is dimensionless).
- v must be m/s.
- a must be m/s².
So if you compute a_c and don’t end with m/s², something is off—usually degrees vs radians, or mixing radius in pixels with speed in meters.
Typical mistakes I see (and how I prevent them)
- Degrees in trig: store θ in radians; convert at the UI boundary.
- Confusing ω with RPM: convert RPM to rad/s via ω = RPM * 2π / 60.
- “Centripetal force is a separate force”: treat it as the net inward force from real forces.
- Numerical drift: parametric updates preserve the circle; force-based updates need care.
- Sign errors in rotation direction: pick a convention (CCW positive) and stick to it.
Implementation patterns: simulation you can run and trust
When I implement circular motion, I decide upfront whether radius is a hard constraint or an outcome.
Pattern A: hard constraint (best for UI and deterministic orbits)
State:
- θ, ω, α
Update:
- ω += α * dt
- θ += ω * dt
- x = r cos θ, y = r sin θ
This keeps the object exactly on the circle (up to float precision). It’s also easy to test.
Below is a complete Python example you can run as python circular_motion.py. It prints a few sample points and estimates speed/acceleration from finite differences to sanity-check the math.
from future import annotations
import math
from dataclasses import dataclass
from typing import List, Tuple
@dataclass
class CircularKinematics:
radius_m: float
theta_rad: float
omegarads: float
alpharads2: float
def step(self, dt_s: float) -> None:
# Semi-implicit Euler for angular state: update omega, then theta.
self.omegarads += self.alpharads2 * dt_s
self.thetarad += self.omegarads * dts
def position_xy(self) -> Tuple[float, float]:
x = self.radiusm * math.cos(self.thetarad)
y = self.radiusm * math.sin(self.thetarad)
return x, y
def finitedifference(values: List[float], dts: float) -> List[float]:
# Simple derivative estimate; good enough for sanity checks.
return [(values[i + 1] - values[i]) / dt_s for i in range(len(values) - 1)]
def main() -> None:
model = CircularKinematics(
radius_m=3.0,
theta_rad=0.0,
omegarads=2.0, # start at 2 rad/s
alpharads2=0.5, # speed up at 0.5 rad/s^2
)
dt_s = 1.0 / 120.0
steps = 600 # 5 seconds
xs: List[float] = []
ys: List[float] = []
for _ in range(steps):
x, y = model.position_xy()
xs.append(x)
ys.append(y)
model.step(dt_s)
# Estimate v and a from x/y samples.
vxs = finitedifference(xs, dts)
vys = finitedifference(ys, dts)
speeds = [math.hypot(vx, vy) for vx, vy in zip(vxs, vys)]
axs = finitedifference(vxs, dts)
ays = finitedifference(vys, dts)
accels = [math.hypot(ax, ay) for ax, ay in zip(axs, ays)]
print("Sample positions (x, y):")
for i in [0, 60, 120, 240, 360, 480]:
print(f"t={i * dt_s:4.2f}s x={xs[i]: .3f} y={ys[i]: .3f}")
print("\nSpeed range (m/s):", f"{min(speeds):.3f} .. {max(speeds):.3f}")
print("Accel range (m/s^2):", f"{min(accels):.3f} .. {max(accels):.3f}")
# Sanity check: average speed should be close to r * average omega.
avg_speed = sum(speeds) / len(speeds)
print("Avg speed (m/s):", f"{avg_speed:.3f}")
if name == "main":
main()
Performance note: this style is cheap. In most real apps, the trig calls are the main cost; even then, for typical UI loads (tens to hundreds of points per frame), it’s usually well under a few milliseconds on modern hardware.
Pattern B: forces drive the motion (best for physics and traction limits)
If you want slipping, snapping ropes, or “orbit decays,” you model forces and integrate acceleration:
- a⃗ = F⃗_net / m
- v⃗ += a⃗ dt
- p⃗ += v⃗ dt
The centripetal requirement becomes a constraint you may fail to meet if you don’t have enough inward force.
A simple real-world example: tire friction has an upper bound. If the required inward acceleration v²/r exceeds what friction can supply, the car doesn’t keep a circular path. In a simulation, that’s not a bug—it’s the point.
A small browser-friendly example (orbiting point)
If you want a quick visual check, here’s a minimal JavaScript snippet you can paste into an HTML file. It uses the “hard constraint” pattern so the orbit stays circular.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener("resize", resize);
resize();
const center = { x: () => canvas.width / 2, y: () => canvas.height / 2 };
const radiusPx = 140;
let thetaRad = 0;
let omegaRadS = 1.8; // rad/s
let alphaRadS2 = 0.0; // set to something like 0.6 to see spin-up
let lastTsMs = performance.now();
function frame(tsMs) {
const dtS = Math.min((tsMs - lastTsMs) / 1000, 0.05); // clamp big frame gaps
lastTsMs = tsMs;
omegaRadS += alphaRadS2 * dtS;
thetaRad += omegaRadS * dtS;
const x = center.x() + radiusPx * Math.cos(thetaRad);
const y = center.y() + radiusPx * Math.sin(thetaRad);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// orbit path
ctx.strokeStyle = "#334155";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(center.x(), center.y(), radiusPx, 0, Math.PI * 2);
ctx.stroke();
// moving point
ctx.fillStyle = "#0ea5e9";
ctx.beginPath();
ctx.arc(x, y, 8, 0, Math.PI * 2);
ctx.fill();
// debug text
ctx.fillStyle = "#e2e8f0";
ctx.font = "14px system-ui";
ctx.fillText(theta=${thetaRad.toFixed(2)} rad, 16, 24);
ctx.fillText(omega=${omegaRadS.toFixed(2)} rad/s, 16, 44);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
Two details here prevent common “works on my machine” issues:
- Clamp dt so a background-tab pause doesn’t teleport your phase.
- Keep θ continuous; don’t mod by 2π unless you truly need bounded values.
What I’d do next in your project (and what to watch for)
If you’re implementing circular motion for a real system—anything from a UI knob to a vehicle sim—I recommend you start by choosing which contract you need: is radius a fixed constraint, or is it something forces must maintain? That decision prevents half the bugs I get called in to debug.
Next, get strict about units. Pick radians internally, convert at the boundary, and add one or two unit-focused tests: RPM to rad/s, degrees to radians, pixels vs meters. In my experience, a tiny unit test suite pays for itself the first time you refactor.
If you need realism, separate tangential acceleration (speed changes) from centripetal acceleration (direction changes). Many “feels wrong” motion systems accidentally blend these and end up with characters that slide sideways when they should speed up forward.
Finally, log the state you actually control—θ, ω, α, and radius—and sanity-check it with simple invariants. For uniform motion, speed should remain nearly constant; for constrained orbits, radius should remain nearly constant. When those drift, it’s usually a time-step or integration issue, not “mysterious physics.”
If you tell me what you’re building (UI orbit, orbital mechanics toy, vehicle turn model, or a full physics loop), I can recommend the best state variables and integration approach for that specific case.


