Koch Snowflake — L-System
A fractal snowflake grown from a single line segment using string rewriting rules.
The Axiom
An L-system is a formal grammar — a set of rules for rewriting strings. At the heart of every L-system is a starting string called the axiom.
For the Koch snowflake, we begin with a triangle:
Axiom: F--F--F
Each F means “draw a line segment forward.” The -- means “turn right 120°.” Three sides, three 120° turns — a triangle.
Stage 0 is the raw axiom rendered: an equilateral triangle. Nothing fractal about it yet.
First Iteration
We apply the production rule once:
F → F+F--F+F
Every F in the axiom is replaced by F+F--F+F. The + means “turn left 60°.”
After substitution:
F+F--F+F -- F+F--F+F -- F+F--F+F
Each edge of the original triangle has sprouted a triangular bump. The perimeter has grown — each straight side is now four segments — but the overall shape is still recognisable as a triangle.
Second Iteration
Applying the rule again replaces every F in the stage-1 string:
Now each of the four segments from stage 1 has itself grown a bump. The snowflake silhouette starts to emerge. Notice that the perimeter length has grown by a factor of 4/3 again — but the area enclosed has grown only slightly.
Third Iteration and the Fractal Limit
At three iterations the Koch snowflake is clearly recognisable. The characteristic jagged, self-similar boundary repeats at every scale.
Mathematically this recursion continues to infinity. The Koch snowflake has an infinite perimeter — each iteration multiplies the perimeter by 4/3, and (4/3)ⁿ → ∞ as n → ∞. Yet the area it encloses converges to a finite value: exactly 8/5 of the original triangle’s area.
This is the paradox at the heart of fractal geometry: a finite region bounded by a curve of infinite length.
def l_system(axiom, rules, n):
s = axiom
for _ in range(n):
s = "".join(rules.get(c, c) for c in s)
return s
axiom = "F--F--F"
rules = {"F": "F+F--F+F"}
for i in range(4):
result = l_system(axiom, rules, i)
print(f"n={i}: {len(result)} characters")
Output:
n=0: 7 characters
n=1: 31 characters
n=2: 127 characters
n=3: 511 characters
Each iteration quadruples the string length (minus the constant -- connectors). By n=6 you have over 16,000 drawing commands — which is why we stop at 3 or 4 iterations for anything you’d want to render on screen.