"A good optimizer does not need to understand the problem — it only needs to recognise a better answer when it finds one. That is what lets the same simple idea redesign an antenna, route a fleet, and balance a power grid."- Claude 2026
Where the vary-select-repeat idea earns its keep — designing physical systems, routing fleets, allocating scarce resources — and how it stacks up against the mathematics it competes with.
A genetic algorithm (GA) is an optimization method — a procedure for searching a large space of possible answers for the best one — that improves a pool of candidate solutions over repeated rounds by keeping the better ones and combining and slightly altering them to make new ones. This page assumes you have met that basic loop and turns to what GAs are actually used for, why practitioners reach for them instead of older methods, and how you would build one for a problem of your own.
By the end of this page you should be able to:
Every application on this page is the same three-step loop pointed at a different problem. A GA holds a set of candidate solutions (the population), each written as a string of values (a chromosome) that encodes one possible answer. The loop then repeats until the answers stop improving:
Describe one possible answer as a string of values the algorithm can hold and change.
A fitness function you supply rates each answer with a single number — the only thing the GA ever learns about the problem.
Selection keeps the best, crossover combines pairs, mutation adds variety — producing the next generation.
Because the algorithm only ever sees the score, the very same loop can be aimed at wildly different problems. The rest of this page is that one idea, applied.
GAs are worth reaching for when a problem has an enormous number of possible answers, when those answers are discrete choices rather than smooth dials, and when there is no clean formula linking a change in the answer to a change in quality — only a way to measure how good a finished answer is. Three application areas show this pattern clearly.
A GA is handed a design and a way to test it, and asked to find a better one. The chromosome encodes the adjustable features; the fitness function runs a simulation that scores the result. Because the algorithm never needs to understand the physics — only to read the score — it can explore designs a human might never try (sometimes called generative design).
Logistics is about order and assignment — which vehicle goes where, in what sequence, at what time. These are combinatorial problems (the answer is an arrangement of discrete pieces, and the number of arrangements grows explosively with size), which suits GAs because a chromosome can directly encode a sequence or an assignment.
Resource management is about allocation — dividing a limited supply (money, energy, materials, staff) among competing demands to get the most value. The chromosome encodes one way of splitting the resource; the fitness function scores that split against the goal while penalising any split that breaks a limit.
Three different industries, one pattern: encode an allocation, score it, let selection-crossover-mutation search for a better one.
A GA is not always the right tool. Traditional methods are often faster and can sometimes prove they have found the genuine best answer — something a GA can never promise. The skill is knowing which to use. Three traditional families are worth contrasting:
Follows the slope of a smooth scoring function downhill to a minimum. Very fast when the function is smooth and you can compute its slope (its derivative), but it needs that slope to exist and can stall in the nearest dip.
Finds the provably best answer when the goal and every limit are straight-line (linear) relationships. Extremely efficient in that narrow setting, but many real problems are not linear.
Checks every possible answer and returns the best. Guaranteed correct, but only feasible when the number of answers is small — it becomes impossible as the problem grows (combinatorial explosion).
The trade-off in one line: traditional methods are faster and can sometimes guarantee the best answer, but only on problems with the right structure (smooth, linear, or small). A GA gives up both speed and any guarantee in exchange for working on almost anything you can score — which is why it earns its place on messy, large, discrete, real-world problems where the others cannot run at all.
Designing a GA is mostly a matter of answering five questions about your problem; the loop itself stays the same. Our case: a delivery company has one van with a weight limit and a list of parcels, each with a weight and a profit. Which parcels go on today's van to earn the most profit without exceeding the limit? (This is the 0/1 knapsack problem — a stand-in for any "pick the best subset under a budget" decision.)
[1, 0, 1, 1, 0, …]
That is the entire design. The code below implements it in plain Python with no external libraries, so every step is visible. The weight limit is handled inside the fitness function by scoring any overweight load as 0 — a simple, common way to make a GA respect a constraint.
import random weights = [3, 2, 4, 2, 5, 3, 6, 4] profits = [8, 5, 9, 4, 10, 7, 12, 6] capacity = 15 n = len(weights) pop_size = 50 generations = 100 mutation_rate = 0.02 tournament_k = 3 def fitness(chromosome): total_weight = sum(w for w, gene in zip(weights, chromosome) if gene) if total_weight > capacity: return 0 return sum(p for p, gene in zip(profits, chromosome) if gene) def random_chromosome(): return [random.randint(0, 1) for _ in range(n)] def select(population): contenders = random.sample(population, tournament_k) return max(contenders, key=fitness) def crossover(parent_a, parent_b): point = random.randint(1, n - 1) return parent_a[:point] + parent_b[point:] def mutate(chromosome): return [1 - gene if random.random() < mutation_rate else gene for gene in chromosome] population = [random_chromosome() for _ in range(pop_size)] for generation in range(generations): population.sort(key=fitness, reverse=True) next_generation = [population[0]] while len(next_generation) < pop_size: child = crossover(select(population), select(population)) next_generation.append(mutate(child)) population = next_generation best = max(population, key=fitness) print('load:', best) print('profit:', fitness(best)) print('weight:', sum(w for w, gene in zip(weights, best) if gene))
next_generation = [population[0]]