Creating a World in 20 Minutes with JavaScript, or a Minimalist Evolution Model
A developer builds a minimalist evolution simulator in JavaScript where mathematical formulas compete, mutate, and breed to discover the shortest expression that correctly calculates the number of days in each month.

I first read about modeling evolution at age 13 in an article called "Living and Dying in a Computer" (Tekhnika Molodezhi, No. 5, 1993). It made such an indelible impression on me that I immediately became fired up to create something similar.
However, I just couldn't work out the laws of the world. How would organisms "look" at the surrounding world? How would they communicate? How would they attack? How would they eat each other? And finally, how would their "brain" be designed? Should I implement a virtual machine, as in the magazine article, or use something simpler, like a finite automaton or a circuit of NAND gates?
In short, the agonies of creation — and, let's be honest, limited technical skills — prevented me from bringing the idea to fruition. I returned to it in adulthood, free of youthful maximalism and perfectionism. I decided: since I can't make a fancy model, I should start with something simpler. And laziness plus remnants of perfectionism whispered: with something maximally simple.
Prologue
There are already a huge number of evolution models — why another one? I want to offer you a maximally simple model that can be created in 20-30 minutes, and interesting results can be achieved almost immediately (not in 4 billion years).
One day I stumbled upon a "perpetual calendar" formula — it calculated the day of the week from a date, accounting for all the nuances of leap year determination. This gave birth to the idea of writing the shortest possible formula that calculates the number of days in a month (without accounting for leap years).
On one hand, the task is simple — write a formula that takes a month number (zero-indexed, January is month zero) and returns the number of days in it. However, there's a catch — the formula must be as short as possible. A perfect opportunity to test genetic algorithms in action!

To make it more interesting, think about how you would implement this. The most obvious option is a simple table lookup. However, there's a clear pattern: months with 30 and 31 days alternate. Though in August this pattern hiccups slightly. And then there's February...
It can be implemented using conditionals, especially in languages that support the ternary operator or logical operators with short-circuit evaluation. But would that be the shortest solution?
While you think about how you'd implement an act of intelligent design, I'll tell you how to make the formula emerge on its own from chaos and primordial mud.
World Design
So, we need to create an evolution model (or implement a genetic algorithm, if you prefer). The organisms in our world will be formulas. Their genetic code is a string with an arithmetic expression. For example, "2+2" is an ordinary inhabitant of this world. Not the most successful one, since survival requires being able to answer the ultimate question of life, the Universe, and everything.
A good inhabitant is a formula that gives more or less close answers, an excellent one gives correct answers for each month, and the ideal is the shortest among them.
Initially, I chose digits, arithmetic operations, and parentheses as building blocks. Obviously, formulas that return a constant value are unlikely to solve our task. A parameter is needed — the month number. It will be called "x". For example, here's another inhabitant of our world: "25+x". And it's actually pretty good — it gives correct answers for June and July! In total, the initial building blocks of the genetic code were the characters:
0123456789+-*/()x
Evolution rests on two main mechanisms: random mutations and natural selection. With natural selection, at first glance, everything is simple. You need to calculate 12 values that the formula produces, compare them with the reference values, and get its fitness — a specific number that can be compared with results of other "organisms" to filter out those that showed the worst results. We'll soon see that in practice it's not all that obvious, and the ability to adapt to the environment sometimes contradicts the grand design.
With random mutations, it's the opposite. At first glance, it seems you can't just randomly change some character in a formula and expect anything worthwhile to come out. Say, when training neural networks, weights change gradually, and before changing them, you determine which direction is better. The network architecture remains unchanged.
In general, everything is very gradual and deliberate. Here, however, changing just one character can lead to radical and even catastrophic consequences. For example, there was a formula 500+x, and it had a child with a mutation — a division sign instead of the first zero: 5/0+x — bang, division by zero. And it was lucky the division sign didn't appear where the five was. /00+x — what kind of monster is that? However, we'll soon see that this isn't actually a particular problem.
So, the inhabitants of this world are formulas, and I wanted to create the world very quickly — in 20-30 minutes. Consequently, the choice of programming language fell on scripting languages that have the extremely unpopular function eval(). At least I didn't have to bother finding a suitable formula interpreter — it comes out of the box.
I first tried Python, but ran into one extremely unpleasant performance problem. Not because Python itself isn't particularly fast, but because it transparently supports arbitrary-precision arithmetic, and in the world of random formulas, something like this can easily appear: x**967**9813. Python tries to compute it without batting an eye, and it doesn't bother it at all that this will take a billion years or so. So I had to switch to good old JavaScript, which has a JIT compiler and arithmetic overflow.
The Primordial Soup
The ideal must be born from complete chaos, so initially the world must be populated with absolutely random inhabitants. I simply generated 100 random strings with lengths from 5 to 20 characters from the set "0123456789+-*/()x".
The overwhelming majority, of course, turned out to be hideous monsters. For example:
87365*)0-2(622
2)3-(510(
)6(7)2+/442/70x+x3
(*x/*5*87
9+8-(/4+84(9
However, some decent ones appeared:
98+651-x
623+15
9984*6
+65*7
Of course, if you feed a monster to the eval() function, you get an exception, so the call needs to be wrapped in a try...catch block. For executable formulas, you need to compare answers with the correct ones and rate how far from the ideal it is. Initially, I simply used the sum of squared differences — the classic LSE. The higher it is, the worse; zero is ideal. Non-executable formulas were assigned a rating of plus infinity.
Since the goal is to get the shortest formula, length should also slightly affect the rating. For example, adding one-tenth of the length to the score.
The eval() function has a "pleasant" property: it executes code in the current context, so it has access to local variables among other things. Usually this is a security threat, but what do we have to fear here? That random formulas will steal our cookies? Thus, if we change the variable x from 0 to 11 in a loop, that value will be used as x in the formula.
The formula evaluation initially looked something like this:
function evaluate(formula) {
try {
for (let x=0; x<12; x++) {
const answer = eval(formula);
// compare with correct answers and rate quality
}
} catch {
return +Infinity; // strictly punish
}
}After calculating the score for each of the hundred formulas, you can simply sort them by score.
However, eval() is rightfully maligned. First I encountered cheater formulas and villain formulas. If a formula contained ++x or x++, it modified the variable used in the loop and skipped some values. Accordingly, it received penalties for not 12 months like normal folks, but only 6 or even fewer. But those are just cheaters, they're not that scary. Far more dangerous were the villains, born to destroy this world. They contained the fragment --x or x--, and this led to an infinite loop.
I fixed the problem with a crude hack of saving the variable before eval() and restoring the value after. After that, everything worked as it should, though not quite as I wanted — but more on that later.
Since good formulas are an order of magnitude fewer than monsters, I decided that only the top 10 would survive, and the rest get tossed out. This frees up 90 spots under the Sun for new inhabitants. Accordingly, ninety inhabitants get a chance to produce offspring. Thus, the ten best survive themselves and also produce offspring, the ten worst simply receive a Darwin Award, and the middle eighty, while they themselves don't survive, do produce offspring.
What is offspring in the world of formulas? Obviously, creating an exact copy of an existing formula makes no sense — it won't give us anything new. So every offspring always has mutations. I chose three possible mutations:
- a randomly chosen character changes to another,
- a randomly chosen character is deleted,
- a random character is inserted at a random position.
Thus, during evolution, formulas can both grow in size and shrink.
At birth, offspring received one of the mutations chosen at random. Character change had a 90% chance, insertion of a new character — 5.5%, deletion — 4.5%. Thus, there was a tendency toward slow growth. It seems like this contradicts our goals — we need to get the shortest formula — but that's exactly why this bias is needed. As a result of natural selection, long formulas die more often, and growth through mutations compensates for this.
So, I created a world where the population always consisted of 100 inhabitants. The lifespan of one world was set to 10,000 generations (which I called epochs out of habit). And since one full simulation took only about 30 seconds, it could be restarted multiple times, yielding parallel worlds (or just other planets).
What follows is the most interesting chapter — what came of all this.
Adaptability
This is roughly what the world's history looked like on screen. What's shown isn't the entire population, but only the rulers (top-1), their year of ascension to the throne, score, and the answers they produce for each month. Naturally, when a new, more advanced formula appears, it takes the throne until an even better formula shows up.

Despite the primordial soup consisting of absolutely random and mostly incorrect formulas, interesting solutions appeared within the first 100 generations (fractions of a second in the real world), sometimes even with partially correct answers.
After that, progress slowed down — new heroes appeared less and less frequently. In theory, the formula that ends up on the throne at the end of the simulation should be the crown of creation, the ideal. In practice, however, evolution got stuck in local minima, and the final king of the world was far from ideal.
On restarts, of course, history took alternative paths — different leaders, different years. However, some formulas turned out to be so resilient that they survived even complete annihilation of all life! That is, even after restarting the simulation from scratch, again from a random primordial soup, the result could be almost the same formula as in previous runs.
One of these phoenixes looked like this:
365/12
Unexpected! Yes, it's short, but this is absolutely not what I was looking for. It doesn't depend on x at all. Though there's logic to it — a month does indeed have 365/12 days on average, and from the least squares perspective, this is actually a fairly close answer to the ultimate question of life, the Universe, and everything.
Sometimes this formula was born in disguise, hard to recognize at first glance:
01332/24
Something's off here... The denominator doubled, but the numerator doesn't look anything like 730. It turns out it actually is 730. The thing is that JS treats numbers starting with zero as written in octal. 01332 (base 8) = 730 (base 10).
There were other fractions with close results:
213/7
517/17
973/32
This categorically didn't satisfy me. January has 31 days, not 30.42! So the laws of this world needed changing. I didn't need fractional answers as the final solution, but I didn't want to completely abandon them either, since they could serve as an intermediate step. So I decided to just penalize them slightly.
if (answer%1 != 0) {
loss += 1;
}Even such mild punishment immediately and radically changed the beauty standards in this world.

From now on, the king was the formula:
30
Exactly what was ordered — an integer! And it even gives the correct answer four times a year!
It's important to understand: this wasn't just the winner of one simulation. This was the winner of every simulation. The formula "every month has 30 days" became the only power in the Universe — on every restart, it ultimately became the crown of creation. In theory, that's exactly how an ideal formula should behave, but from the perspective of the Ultimate Question, this formula is far from the absolute truth.
Nevertheless, it was useful — it reminded me that getting the exact answer is also an important criterion. LSE works well when answers are far from expected, but it's too lenient with close (but wrong) answers. I needed to sprinkle in some perfectionism.
if (answer != expected) {
loss += 1;
}And the ruler of the Universe immediately changes! Now it's... TA-DA!

31
Short? Short. Integers? Integers. Correct answers? In 7 out of 12 cases! Much better than the previous leader!
Something needed to change radically. The laws of physics needed to be altered so that nonsense would be fundamentally impossible.
The common problem with all the above leader formulas is that they're actually constants and don't depend on x at all. Therefore, I introduced a new commandment: the formula must contain "x". Formulas without "x" are to be strictly punished.
if (formula.indexOf('x') == -1) {
loss += 10000;
}Now I'd surely get something worthwhile! I got...

The harsh punishment for lacking x didn't go unnoticed. Evolution immediately responded with the perfect solution:
31//x
If your native language is Python, you're probably puzzled as to why integer division is needed here. But this is JavaScript, baby. Here // is the start of a comment.
Do you understand what happened? This is the same formula as before — the one that always equals 31, but now it carries x in its pocket just in case someone checks. Evolution, this process without any consciousness, this process with only natural selection and random mutations, simply went and created a lifehack. Broke the system.
Moreover, as the history shows, the "ideal" was born as early as generation 37, and after that it just shook trash out of its pockets. What to do about this? Continue the stick method — ban comments.
if (formula.indexOf('//') != -1) {
loss += 10000;
}Surely now there are no loopholes left, I thought. There were...
31+x-x
31+x*0
31+0x0
The last one is especially impressive. Yes, indeed, eval() has access to local variables and automatically substitutes them... Except when "x" is not a variable but part of a hexadecimal number.
And sometimes, very rarely, something like this appeared:
31/*x*/
In response to the comment ban, evolution simply laughed in my face. What a blessing that I'm using a very limited alphabet. It's quite possible that if it had letters at its disposal, it could have rewritten the evaluation function. Or stolen cookies.
The persistence of constants and their unrelenting will to live was hilarious on one hand. On the other, they needed to be defeated once and for all. And I banned formulas from producing the same answer for every month.
if ((new Set(answers)).size == 1) {
loss += 100000;
}Now constants had not a single chance of holding the lead.
I hoped this would finally give a boost to rapid formula development, and formulas that give the correct answer for all 12 months would start appearing, even if they were incredibly long, bulky, and convoluted. Expectations failed again. The new permanent leader:
30+x/11
This is simply linear regression from 30 (in January) to 31 (in December). In terms of quality, it actually got worse: only two integer answers, only one correct. I increased the number of epochs to 100,000, did several restarts — all for nothing. This formula (and various variations of it) dominated.
I was already feeling disappointed with the whole endeavor and wanted to quit, when I smacked my forehead! Of course! Linear regression! What did I expect? I only have arithmetic operations. Sure, you can define nonlinear functions with them: polynomial and exponential, you can even raise (-1) to the power of x to get the much-needed "picket fence." But still, arithmetic operations are probably just not enough to solve the problem. And I granted the inhabitants new tools.
Optimizing the Laws of the Universe
Even adding just one operation — modulo division — already moved the world off dead center. A cool formula appeared:
31-x%7%2
Modulo 2 division, or parity check, is a simple way to set up the alternation of months with 30 and 31 days, and modulo 7 division resets the alternation between July and August. The result — 11 correct answers out of 12! Only February doesn't fit the pattern.
Then I generously added the remaining operations: bitwise operations, comparison operators, the ternary operator, etc. Now the alphabet looked like this:
0123456789+-*/()x%?:|&^<>=!~
Strangely, the result only got worse. Suddenly, this formula started conquering the galaxy:
5%x|30
You know how in sci-fi series — wherever the heroes fly, even on the most remote planets, there are still humanoids. The official version is that under similar conditions, evolution develops similarly, and you end up with something human-like. Though we understand it's because arachnids and silicon-based lifeforms aren't very eager to sign contracts with film studios, and it's easier for human actors to put on makeup than to act out a giant amoeba.
Nevertheless, the version about similar evolution on different planets was confirmed: in roughly 80% of restarts, evolution arrived at precisely 5%x|30, even though it's inferior to 31-x%7%2.
One reason is that some formulas have a higher chance of appearing and taking a place under the Sun than others, so statistically they appear earlier. For example, since 31 in binary is 11111, and 30 is 11110, the bitwise OR operation, designed to set bits to one, is very favorable for this task. As soon as OR appears in a formula, its chances of breaking into the leaders skyrocket. And then it's just a matter of tuning the coefficients.
But what prevents cooler formulas from appearing a bit later and stealing the spotlight? The reason turned out to be nepotism. I added code that, after the simulation ends, displays who ended up living in this world — just the top 10.

Despite the world existing for 10,000 epochs, after epoch 376 there were no upheavals — the leader took the throne and stayed on it until the end. All pretenders were close relatives with insignificant differences.
Not only did evolution get stuck in a local minimum, but the entire world was captured by a single dominant species. To prevent this, I added clone protection: there shouldn't be two formulas in the world giving the same result. If that happens, the shorter one stays.
Alas, this didn't produce dramatic changes — relatives just became more distant. 5%x|30 continued to appear in almost every restart, though sometimes it did yield to its fourth cousin, who now had a higher chance of being born:
3/x^31
Here's a rather interesting construction that abuses JS arithmetic. Division by x immediately catches the eye, but when x = 0, you get division by zero! JS, unlike many other languages, doesn't consider division by zero a cause for panic — you just get +Infinity. In JS, bitwise operations convert their operands to int32 before executing (even though regular numbers in JS, even integers, are always float64). Infinity from such a conversion becomes zero! 0 xor 31 = 31, and that's exactly how many days are in month zero, January.
What's wonderful about this construction is that for x=1 you get 3 xor 31 = 28. Thus, this is that rare formula that correctly calculates days in February! And for x > 3, the result is less than one, which thanks to the bitwise operation also becomes zero.
The next change, helping escape local minima, was multiple mutations. Some operations require changing or adding at least two characters. If you add only one, the function becomes invalid. To let evolution jump farther, I made it so offspring undergo one to three mutations at birth.
This time the impact was better. 5%x|30 almost stopped appearing. Result diversity increased. For example, these formulas were born:
x%7+1|30
x%7^9|22
x%7^29|6
x^7>x|30
They also correctly calculate for 11 months — all except February. Or this evil genius that correctly handles February:
28|7+!--x
Overall, variety increased, but excellent ones — those giving the correct answer for all 12 months — still didn't exist. And notably — there were absolutely no parentheses or ternary operators in the formulas. I tried increasing their probability, then removed them entirely — it was a step backward each time. And then I decided on a desperate move — sexual crossbreeding.
On the Path to the Ideal
The main bottleneck remained February. The first approach was cautious: crossbreed only the two best formulas via arithmetic mean: ((formula1)+(formula2))/2. The result was too good — it led to explosive genome growth.
Then I changed the crossbreeding method: formula1+formula2. No parentheses and no division by two. The result would be obviously worse — and that's beautiful! It means it won't immediately take a spot in the top, but will remain somewhere in the middle, gradually mutating until something interesting emerges. And it worked!
For the first time, I managed to get a formula that correctly solves for each of the 12 months:
36-5^7<9^7+3<x+9*2%4^35<-6^7<x&5^7+2<x+3*2%9^3<3<62>x%5^2/x
For comparison, here's a hand-written formula that does a table lookup:
[31,28,31,30,31,30,31,31,30,31,30,31][x]
Evolution's result is longer than intelligent design, but this is already an incredible success! After several restarts, a shorter version was born:
x+8%8^71772%x%9^x<8|6+x*2|24+4%7^8&7
I still didn't want to analyze and optimize manually. Before the next restart, I abused my omnipotence and placed this already-finished formula in the primordial soup — like an astronaut with potatoes landing on a virgin planet. Naturally, the formula immediately became the ruler, and the only mutations that could improve the result were removing something unnecessary. I changed mutation probabilities — now deletion became the most probable.

And so, step by step, the IDEAL was obtained.
2/x^30|x^7>x
One part resembles the old acquaintance who abused division by zero to solve the February problem. The other — the old acquaintance who could solve everything except February. The crossbreeding was successful!
Conclusion
Even such simple evolution modeling makes you look at the world differently. On one hand, we show that mutations and natural selection can create something ordered from complete chaos. On the other hand, we act as the Demiurge who has to control the process and tune parameters.
We saw how evolution can adapt to circumstances and break the system. On the other hand, we saw how that same evolution ignores certain possibilities. In the model — parentheses and the ternary operator. In life — it's not for nothing they say humanity's greatest invention is the wheel. Energy, aviation, neural networks — nature created all of these long before humans appeared. But the wheel, nature somehow ignored.
We saw that one way to escape a local minimum is to start everything from scratch. A complete restart. In Earth's history, mass extinctions gave evolution the opportunity to escape local minima — like the extinction of dinosaurs, which gave mammals their chance.
We saw how even an organism far from ideal can become the dominant species. However, somewhere in an isolated place, something far cooler can develop. And if they suddenly meet, the first one won't stand much of a chance. Therefore, designing the Universe so that inhabited planets are very far from each other is a very sound design decision — it gives us a head start.
Finally, we're surrounded by things that from an evolutionary standpoint aren't particularly needed. Take philosophy — it doesn't particularly help with survival and reproduction. Nevertheless, it appeared from somewhere, not thanks to evolution but despite it. Perhaps not everything in this world is subject exclusively to evolutionary processes.