HomeArticlesFractals

L-Systems and Plant Growth

A 1968 grammar for modelling algae cell division turns out to grow convincing trees, ferns and coastlines.

mysimulator teamUpdated July 2026≈ 9 min read▶ Open the simulation

A grammar for growth

In 1968, botanist Aristid Lindenmayer devised a string-rewriting formalism to model how algae cells divide. An L-system is a formal grammar G = (V, ω, P): an alphabet V of symbols, an axiom ω (the starting string), and a set of production rules P mapping each variable to a replacement string. What sets L-systems apart from a standard Chomsky grammar is that every symbol in the string is rewritten simultaneously at each generation — parallel replacement, not sequential substitution — and that is exactly what produces self-similar, branching structure.

Algae (Lindenmayer's original 1968 example):
  Variables: A B      Axiom: A      Rules: A → AB, B → A

  Gen 0: A
  Gen 1: AB
  Gen 2: ABA
  Gen 3: ABAAB
  Gen 4: ABAABABA        // length grows as Fibonacci numbers: 1,2,3,5,8,13…
live demo · procedural branching grammar● LIVE

Turtle graphics: giving the string a shape

An L-system string only becomes a picture once each character is interpreted as a command for a turtle holding a position, a heading angle, and a stack for branching: F moves forward drawing a segment, +/ turn left/right by a fixed angle δ, and [/] push and pop the turtle's state. The bracket pair is what creates a branch: push at the base, draw the side shoot, pop back to exactly where the branch started.

Fractal Plant (δ=25°):  axiom X, rules  X → F+[[X]−X]−F[−FX]+X,  F → FF
Koch curve (δ=60°):     axiom F, rule   F → F+F−−F+F      // D = log4/log3 ≈ 1.262
Sierpiński (δ=60°):     F−G−G, F→F−G+F+G−F, G→GG          // D = log3/log2 ≈ 1.585

JavaScript: expand then draw

The engine has two independent phases: expand the axiom for n generations, then walk the resulting string once on Canvas 2D.

function expand(axiom, rules, gens) {
  let str = axiom;
  for (let g = 0; g < gens; g++)
    str = [...str].map(ch => rules[ch] ?? ch).join('');
  return str;
}

function draw(ctx, str, x, y, angle, step, delta) {
  const stack = [];
  ctx.beginPath(); ctx.moveTo(x, y);
  for (const ch of str) {
    if (ch === 'F') { x += step*Math.cos(angle); y -= step*Math.sin(angle); ctx.lineTo(x, y); }
    else if (ch === '+') angle += delta;
    else if (ch === '-') angle -= delta;
    else if (ch === '[') stack.push({ x, y, angle });
    else if (ch === ']') ({ x, y, angle } = stack.pop());
  }
  ctx.stroke();
}

A rule like F → FF roughly doubles the string length every generation — generation 10 already produces 1,024 segments, generation 20 over a million. Past a handful of generations it is far cheaper to skip materialising the full string and interpret the grammar recursively with a depth counter, producing the identical drawing in constant stack space.

Stochastic rules and 3D turtles

A deterministic rule can be replaced by several alternative productions, each with a probability that sums to 1 — running the same grammar twice then produces two different-looking plants, essential for avoiding the artificial uniformity of a purely deterministic system. In 3D, the turtle carries a full heading/left/up rotation frame instead of a single angle, and the symbols & ^ \ / apply rotation matrices around that frame's axes, letting a branch twist and pitch in space before F commits a segment to a Three.js LineSegments buffer.

Beyond closed grammars

Prusinkiewicz and Lindenmayer's 1990 book The Algorithmic Beauty of Plants extends the basic model considerably: parametric L-systems attach numbers to symbols (F(l,w)) so branches can taper with each generation; open L-systems exchange information with a simulated environment, letting a light-field simulation starve shaded branches and produce realistic shade avoidance; and context-sensitive rules condition a replacement on neighbouring symbols, modelling the inter-cell chemical signalling that motivated Lindenmayer's original biology.

Frequently asked questions

What makes L-system rewriting different from a normal grammar?

Every symbol in the string is replaced simultaneously in each generation, rather than one symbol at a time as in a standard Chomsky grammar. That parallel rewriting is what produces naturally self-similar, branching structure instead of an arbitrary derivation sequence.

How do the push [ and pop ] symbols create branches?

[ saves the turtle's current position and heading onto a stack; ] restores the most recently saved state. Everything drawn between a matching pair happens on a side branch that returns exactly to its starting point afterward — the fundamental idiom behind every tree, shrub and fern L-system.

Why do rules like F → FF cause the string to explode in length?

Because the string length roughly doubles every generation, so generation 10 already produces 1,024 segments and generation 20 over a million. High-generation L-systems should skip materialising the full string and instead use a recursive interpreter with a depth counter, which gives the same drawing in constant stack space.

Try it live

Change the axiom, rules and iteration depth and watch the plant regrow instantly in L-Systems — Procedural Plants.

▶ Open L-Systems simulation

What did you find?

Add reproduction steps (optional)