The recursive tree is a rite of passage. You write a function that draws a line, splits, and calls itself twice with a shorter length and a rotated angle, and you get something tree-shaped. It is deeply satisfying and it is not how real growth works.
The Coding Train’s fractal tree — the right starting point, and the thing everything below improves on.
Start: the naive recursive tree
function branch(len) {
line(0, 0, 0, -len);
translate(0, -len);
if (len > 4) {
push(); rotate(angle); branch(len * 0.67); pop();
push(); rotate(-angle); branch(len * 0.67); pop();
}
}
Perfectly symmetrical, self-similar at every scale, and recognisably artificial. Two things give it away: every branch splits the same way, and branches happily grow through each other.
L-systems: growth as string rewriting
Lindenmayer systems, devised by the biologist Aristid Lindenmayer in 1968, model growth as repeated substitution on a string of symbols.
Three parts:
- An alphabet of symbols
- An axiom — the starting string
- A set of production rules — what each symbol becomes
The classic binary tree:
axiom: F
rule: F → F[+F]F[-F]F
Apply the rule to every F, repeatedly. Then interpret the resulting string as turtle graphics:
| Symbol | Means |
|---|---|
F | draw forward |
+ | turn left by δ |
- | turn right by δ |
[ | push position and heading onto a stack |
] | pop them |
The brackets are the whole reason this works for plants. [ and ] give you a stack, so a branch can be drawn and then the turtle returns to where it diverged. Without them you can only draw a single path.
Why bother, if recursion does the same thing? Because the rewriting is declarative. The structure lives in the rules, not in control flow, which means you can mutate rules, interpolate between rule sets, store them as data, and evolve them genetically. A recursive function hard-codes one plant; a rule set is a parameterised space of plants.
The three extensions that make L-systems look alive
The textbook L-system still produces something too regular. Three additions fix it, and they are the difference between an exercise and a usable tool.
1. Stochastic rules. Give a symbol several possible productions with probabilities:
F → F[+F]F (p = 0.4)
F → F[-F]F (p = 0.4)
F → FF (p = 0.2)
Now every run differs and no two branches are identical. This single change does more for plausibility than any amount of tuning.
2. Parametric rules. Attach parameters to symbols — F(length), A(age, vigour) — and write rules that transform them: F(l) → F(l × 0.8). This is how you get branches that taper, and how growth responds to a simulated resource.
3. Context-sensitive rules. Make a production depend on neighbours: A < B > C → D means B becomes D when preceded by A and followed by C. This is how signals propagate through a structure — nutrients moving up, hormones moving down — and it is how you model a plant responding to its own state.
The definitive reference is Prusinkiewicz and Lindenmayer’s The Algorithmic Beauty of Plants (1990), which is free online and is one of the best technical books in this entire field.
Space colonization: the other approach
L-systems grow from rules. Space colonization grows toward opportunity.
The algorithm, three steps repeated:
- Search — active points find unused attractor points within a fixed radius
- Connect — each attractor chooses the closest proposing parent
- Advance — newly connected points become active
You seed a cloud of attractor points defining the volume to be filled, plus one or more starting points. Branches grow toward unclaimed attractors, and each attractor is consumed once.
The consequence is competition. Two branches cannot grow into the same region, because whichever arrives first claims the attractors. That is why the output looks organic: real branching structures are shaped by competition for space, in trees, lungs, river deltas, blood vessels and lightning.
From Runions et al. (2005), originally for modelling leaf venation.
Which to use
| L-systems | Space colonization | |
|---|---|---|
| Control | Rules and grammar | A volume of attractors |
| Fills a given shape | Awkwardly | Natively |
| Self-similar detail | Natively | Less so |
| Animating growth | Per-iteration | Per-step, very naturally |
| Evolvable | Yes — rules are genomes | Harder |
| Best at | Plants, fractals, structure with rules | Filling a form, veins, lightning, roots |
The practical rule: if you need the structure to fill a particular shape, use space colonization. Painting attractor points into a volume — a word, a body, a logo — and letting branches grow to fill it is the cleanest way to get organic growth into a defined form, and it is almost impossible with an L-system.
If you need self-similar detail or want to evolve the structure, use an L-system.
And you can combine them: an L-system for the trunk’s major structure, space colonization for the fine branching into a canopy volume.
Where to run them
p5.js / Processing — best for learning. The turtle interpreter is thirty lines.
Houdini — space colonization in geometry nodes, and we wrote today about Karlis Stigis feeding a space-colonization network’s branch directions into a Pyro velocity field to steer smoke, which is the most interesting use of this we have seen recently.
Blender geometry nodes — both are buildable; L-systems are awkward without scripting, space colonization is natural.
Grasshopper — strong for L-systems, and the architectural community has deep resources.
And cl-l-system, lpy (L-Py) and the CPFG/L-studio lineage from the Algorithmic Botany group if you want the research-grade tooling.