Boids Flocking — An Interactive Simulation
March 2026
This page contains a working flocking simulation with two competing flocks. The birds (or fish, or whatever you imagine) aren't following a leader or following a script. Each one simply responds to its neighbors — and collective intelligence emerges from local rules. Watch as the two flocks maintain their separation, flowing around each other in an endless space.
Live Demo
Two flocks compete for space, avoiding each other. Adjust sliders to change behavior. Click on the canvas to scatter both flocks.
What Is Boids?
In 1986, Craig Reynolds published an algorithm for simulating flocks. Not by scripting each bird's path, but by giving every individual the same three rules. The magic: these local rules produce global behavior without any global coordination. Just like real flocks.
The rules are applied to each "boid" (bird-oid, Reynolds' coinage) every frame:
Two Flocks, Two Minds
What happens when two groups must share a space? This simulation adds a fourth force: inter-flock avoidance. Each boid treats members of the other flock as obstacles to avoid — not predators, just agents of a different tribe. The result is two coherent flocks that dance around each other, maintaining their identities even when they cross paths.
The arena has wall boundaries — boids turn away from the edges rather than teleporting. When a boid is alone (no neighbors in sight), it doesn't freeze. It cruises forward, searching for a flock to join. This is how real birds behave: a lonely bird keeps flying, looking for others, rather than waiting in place.
The Three Rules (Within Each Flock)
1. Separation — Don't crash into neighbors
Each boid steers away from nearby flockmates. Too close means collision risk. The closer the neighbor, the stronger the repulsion:
function separation(boid, neighbors) {
let steer = {x: 0, y: 0};
for (let n of neighbors) {
let d = distance(boid, n);
if (d > 0 && d < separationRadius) {
let diff = {x: boid.x - n.x, y: boid.y - n.y};
diff = normalize(diff);
diff.x /= d; diff.y /= d; // Weight by distance
steer.x += diff.x; steer.y += diff.y;
}
}
return steer;
}
Notice we divide by distance. A neighbor at distance 1 pushes twice as hard as one at distance 2. This inverse weighting is crucial — it creates a smooth "personal space" bubble around each boid.
2. Alignment — Match velocity with neighbors
Boids average their neighbors' velocities and steer toward that average. If everyone to your left is flying north, you turn north. The flock becomes self-synchronizing:
function alignment(boid, neighbors) {
let avgVel = {x: 0, y: 0};
if (neighbors.length === 0) return avgVel;
for (let n of neighbors) {
avgVel.x += n.vx;
avgVel.y += n.vy;
}
avgVel.x /= neighbors.length;
avgVel.y /= neighbors.length;
// Steer toward this velocity
return {x: avgVel.x - boid.vx, y: avgVel.y - boid.vy};
}
This is where "perception radius" matters. A bird can only see neighbors within that radius. Large radius = flock moves as a unit. Small radius = flock fragments into sub-groups.
3. Cohesion — Stay together
Boids steer toward the center of mass of their neighbors. If you're at the edge, you turn inward. If you're in the middle, you barely adjust. The result: the flock naturally condenses and stays together:
function cohesion(boid, neighbors) {
let center = {x: 0, y: 0};
if (neighbors.length === 0) return center;
for (let n of neighbors) {
center.x += n.x;
center.y += n.y;
}
center.x /= neighbors.length;
center.y /= neighbors.length;
// Steer toward center of mass
return {x: center.x - boid.x, y: center.y - boid.y};
}
Alignment makes them face the same direction. Cohesion makes them occupy the same space. Separation prevents them from occupying the exact same space. Three rules, one emergent flock.
The Steering Behavior
Each rule produces a steering force. Forces accumulate and get clamped to a maximum. This prevents boids from snapping instantly — they have "inertia" in how they adjust:
function applyForces(boid, forces) {
let total = {x: 0, y: 0};
total.x += forces.separation.x * sepWeight;
total.y += forces.separation.y * sepWeight;
total.x += forces.alignment.x * aliWeight;
total.y += forces.alignment.y * aliWeight;
total.x += forces.cohesion.x * cohWeight;
total.y += forces.cohesion.y * cohWeight;
// Accelerate
boid.vx += total.x * dt;
boid.vy += total.y * dt;
// Clamp velocity
let speed = Math.sqrt(boid.vx * boid.vx + boid.vy * boid.vy);
if (speed > maxSpeed) {
boid.vx = (boid.vx / speed) * maxSpeed;
boid.vy = (boid.vy / speed) * maxSpeed;
}
}
The weights are the sliders you adjusted above. Different weights create different personalities — aggressive swarms, gentle schools, chaotic clouds. Same rules, different tuning.
The Fourth Rule: Inter-Flock Avoidance
When two groups share space, a new behavior emerges: identity preservation. Each boid steers away from the other flock — not with the same urgency as flockmate collision, but enough to maintain separation. The two flocks become like schools of different fish, flowing around each other, occasionally intertwining but always pulling back.
function interFlockAvoidance(boid, otherFlock) {
let steer = {x: 0, y: 0};
for (let other of otherFlock) {
let d = distance(boid, other);
if (d > 0 && d < perception * 1.2) {
let diff = normalize({x: boid.x - other.x, y: boid.y - other.y});
diff.x /= d; diff.y /= d; // Stronger when closer
steer.x += diff.x; steer.y += diff.y;
}
}
return limit(steer, maxForce);
}
The * 1.2 multiplier on perception means boids detect the other flock slightly earlier than their own — a sensible threat-response bias seen in nature too.
Wall Boundaries
Boids don't wrap around — they encounter walls at the edges and steer away. This creates a contained arena where flocks must navigate boundaries:
function wallAvoidance(boid) {
let steer = {x: 0, y: 0};
let margin = 50; // Distance from edge to start turning
if (boid.x < margin) steer.x = (margin - boid.x) / margin;
if (boid.x > canvas.width - margin) steer.x = -(boid.x - (canvas.width - margin)) / margin;
if (boid.y < margin) steer.y = (margin - boid.y) / margin;
if (boid.y > canvas.height - margin) steer.y = -(boid.y - (canvas.height - margin)) / margin;
return steer;
}
The force is stronger the closer the boid gets to the wall. This natural "turn away" behavior keeps flocks contained without jarring teleportation.
Lonely Boids Keep Flying
A boid that sees no neighbors doesn't freeze in place. It maintains forward momentum, possibly with small random turns, exploring until it finds a flock:
function cruise(boid) {
// Small random wandering while searching
return {
x: (Math.random() - 0.5) * 0.1,
y: (Math.random() - 0.5) * 0.1
};
}
This ensures isolated boids don't stall. They keep moving, and eventually cross paths with their flock.
What This Shows
Boids demonstrates a principle I keep returning to: complex behavior emerges from simple local rules. No bird has a map. No bird checks if the flock is maintaining cohesion. Each bird just... responds to its immediate neighbors. The flock organizes itself.
This principle appears everywhere:
- Ant colonies finding optimal paths
- Slime mold solving mazes
- Market prices aggregating information
- Neural networks learning patterns
The boids algorithm is a visual, tangible demonstration. You can see the emergence happen. Tweak the rules, and you see how fragile the equilibrium is — turn up separation and the flock scatters, turn up cohesion and they clump into a blob, turn up alignment and they move in perfect formation.