Quick answer: A genetic algorithm maintains candidate solutions, evaluates a fitness function, selects parents, creates new candidates through crossover and mutation, and repeats. The objective direction, representation, randomness, stopping rule, and validation experiment determine whether the search is meaningful.

A genetic algorithm is an optimization technique inspired by evolution. It keeps a population of candidate solutions, scores each candidate with a fitness function, selects better candidates, and creates new candidates through crossover and mutation. A genetic search can approximate constrained selection problems, while Knapsack Problem in Python: 3 Solutions compares brute force, memoization, and dynamic programming on knapsack directly.
Genetic algorithms are useful when a search space is large, rough, or hard to optimize with direct formulas. They are common in scheduling, feature selection, parameter tuning, route planning, and toy optimization problems where a good solution is enough.
They do not guarantee the perfect answer. Instead, they explore many candidates and try to improve the population over time. That makes them practical for problems where checking a candidate is easier than calculating the best candidate directly.
The DEAP documentation and PyGAD documentation cover established libraries. This guide uses small pure-Python examples so the moving parts are easy to see. For related array operations, see NumPy repeat.
Create A Candidate
A candidate is one possible answer. For a simple binary problem, represent each candidate as a list of zeros and ones.
import random
def make_candidate(length):
return [random.randint(0, 1) for _ in range(length)]
candidate = make_candidate(8)
print(candidate)
Real projects may use numbers, strings, routes, model settings, or custom objects. The representation should make crossover and mutation possible.
Choose the representation carefully because every later step depends on it. A candidate should be easy to score, copy, combine, and change in small ways.
Score Fitness
The fitness function decides how good a candidate is. In this toy example, more ones means a better score.
def fitness(candidate):
return sum(candidate)
print(fitness([1, 0, 1, 1]))
print(fitness([0, 0, 1, 0]))
The fitness function is the most important part of the algorithm. If it rewards the wrong behavior, the search will move in the wrong direction.
Good fitness functions are consistent and cheap enough to run many times. If scoring one candidate is expensive, population size and generation count become important performance controls.
Select Parents
Selection chooses candidates that will produce the next generation. A simple approach is to keep the highest-scoring candidates.
def select_parents(population, count):
ranked = sorted(population, key=fitness, reverse=True)
return ranked[:count]
population = [
[1, 0, 1, 0],
[1, 1, 1, 0],
[0, 0, 1, 0],
]
print(select_parents(population, 2))
Selection pressure controls how strongly the algorithm favors current winners. Too much pressure can reduce diversity too early.
Keeping some variety in the population helps the algorithm explore. If every candidate becomes nearly identical too quickly, crossover and mutation have less useful material to work with.

Apply Crossover
Crossover combines two parents into a child. One-point crossover swaps the tail of two lists after a split point.
def crossover(left, right):
point = len(left) // 2
return left[:point] + right[point:]
parent_a = [1, 1, 0, 0, 1, 0]
parent_b = [0, 0, 1, 1, 0, 1]
print(crossover(parent_a, parent_b))
Crossover works best when useful building blocks can be shared between candidates. If the representation is poor, crossover may mostly create broken children.
For some problems, a different crossover method is better than a simple halfway split. The operator should respect the structure of the candidate, such as routes, schedules, or parameter ranges.
Apply Mutation
Mutation makes small random changes. It helps the search escape local patterns and keeps the population diverse.
import random
def mutate(candidate, rate=0.1):
child = candidate[:]
for index, bit in enumerate(child):
if random.random() < rate:
child[index] = 1 - bit
return child
print(mutate([1, 1, 1, 1], rate=0.5))
A mutation rate that is too high can turn the search into random noise. A rate that is too low may make the population converge too early.
Mutation is the exploration step. It introduces new possibilities that are not present in the selected parents, which can help the search escape a weak local result.

Run A Small Genetic Algorithm
This compact loop creates a population, selects parents, creates children, mutates them, and tracks the best candidate.
import random
def run(length=8, size=20, generations=10):
population = [make_candidate(length) for _ in range(size)]
for _ in range(generations):
parents = select_parents(population, size // 2)
children = []
while len(children) < size:
left, right = random.sample(parents, 2)
child = mutate(crossover(left, right), rate=0.05)
children.append(child)
population = children
return max(population, key=fitness)
best = run()
print(best, fitness(best))
This example is intentionally small. Production genetic algorithms need better selection, stopping rules, constraints, logging, and repeatable random seeds.
Stopping rules can use a fixed generation count, a target score, or a limit on generations without improvement. Logging the best score per generation helps show whether the search is still making progress.
Best Practices
Start by defining a clear fitness function and a representation that can be changed safely. Then choose population size, mutation rate, crossover method, and stopping rules. Track the best score over time so you can see whether the search is improving.
Use libraries such as DEAP or PyGAD when you need mature operators, constraints, parallel evaluation, or experiment tooling. Use a hand-written version only when learning, prototyping, or solving a small custom problem.
The reliable pattern is to keep the first version simple, test each operator independently, and measure whether each generation improves the score. A genetic algorithm is useful only when the fitness function and representation match the real optimization goal.
Define A Candidate Representation
Choose a chromosome or candidate structure that can express valid solutions. If most random mutations create invalid candidates, add repair or constraint-aware operators instead of hiding failures in the fitness score.

Make Fitness Direction Explicit
Decide whether larger or smaller scores are better and normalize penalties consistently. A sign error can make selection favor the worst candidates while the population still appears to change.
Balance Selection Pressure
Strong selection exploits good candidates but can remove diversity early. Tournament, rank, or weighted selection should be tested against population collapse and premature convergence.

Use Crossover And Mutation Together
Crossover recombines existing structure, while mutation explores new structure. Rates depend on the representation and problem; log them and evaluate sensitivity rather than using one magic constant.
Preserve Good Candidates Deliberately
Elitism can retain the best solution, but too much elitism reduces exploration. Keep a copy of the best candidate and verify that it remains valid after every generation.
Repeat Seeded Experiments
Random search results vary. Run multiple seeds, report best and typical fitness, track generation history, and compare against a simple baseline so one lucky run is not presented as a reliable improvement.
The official Python random documentation covers reproducible generators. Related Python Pool references include run logging and testing.
For related optimization workflows, compare run histories, seeded tests, and randomness when evaluating a genetic algorithm.
Frequently Asked Questions
What is a genetic algorithm?
It is a population-based search method that evaluates candidate solutions, selects promising candidates, and creates new candidates through variation.
What is a fitness function?
The fitness function scores how well a candidate meets the optimization objective; its direction and scale must be defined clearly.
What do crossover and mutation do?
Crossover combines parts of parent candidates, while mutation introduces random changes that maintain exploration.
How do I avoid a genetic algorithm stopping too early?
Use a meaningful population, mutation and selection balance, a stable stopping rule, and repeated seeded experiments to distinguish convergence from luck.