Last quarter I had to plan a 12-stop delivery loop and the route my gut picked came out 38% longer than the version a small Python script returned in under a second. That gap is the travelling salesman problem (TSP): given a set of cities and the distance between every pair, find the shortest loop that visits each city once and returns to the start.
TSP is NP-hard in general, but two simple methods cover the small and medium cases we actually meet in shipping scripts.
Greedy nearest-neighbour runs in O(n²). Brute force tries every permutation and gives you the true optimum up to about ten cities.
Both fit on a screen. Both run on a laptop.
The problem in one paragraph
Ten Indian cities, one salesman, one loop. The job is to visit every city exactly once and return to the start while minimizing the total distance. The order of visits is the only choice you make.
If you try every permutation of ten cities, you have 9! = 362,880 options and the script finishes in about six seconds.
Twelve cities is 11! ≈ 40 million and the wait is closer to a minute.
Fifteen cities is 14! ≈ 87 billion, and brute force stops being a workable option.
Greedy nearest-neighbour algorithm
Start at any city. Repeatedly jump to the unvisited city closest to where you stand. When every city is visited, close the loop back to the start.
The algorithm picks locally optimal moves and does not look ahead. That keeps the cost to O(n²), because every city picks its nearest neighbour among the rest. The code is short enough to hold in your head.
import math
def distance(c1, c2):
return math.sqrt((c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2)
def tsp_greedy(cities):
n = len(cities)
visited = [False] * n
start = 0
visited[start] = True
tour = [start]
total = 0.0
current = start
for _ in range(n - 1):
nxt = None
best = float('inf')
for i in range(n):
if not visited[i]:
d = distance(cities[current], cities[i])
if d < best:
best = d
nxt = i
visited[nxt] = True
tour.append(nxt)
total += best
current = nxt
total += distance(cities[current], cities[start]) # close the loop
return tour, total
cities = [(2, 4), (1, 8), (7, 1), (8, 5)]
tour, total = tsp_greedy(cities)
print('Tour:', tour)
print('Total distance: {:.4f}'.format(total))
Run it on a 4-city example and the output looks like this:
Output
Tour: [0, 1, 3, 2]
Total distance: 21.6929
Tour indices 0, 1, 3, 2 means we leave city 0, then 1, then 3, then 2, then return to 0. Total distance is 21.69. Greedy is fast and the result is a valid tour, but it is not always optimal.
On random inputs greedy typically lands 10% to 25% above the optimum. That is good enough for sizing a delivery loop and bad enough to mislead a sales dashboard.
Brute force: try every permutation
Brute force is honest. It builds the full distance matrix once, then iterates over every permutation of the city indices, computes each tour length, and keeps the minimum. The complexity is O(n!) for the permutations plus O(n) per tour, which lands at O((n+1)!).
The number of permutations balloons fast, so brute force is a learning tool for small instances, not a production solver. For n=10 the work fits in a few seconds. For n=15 the loop will not finish on your laptop.
import numpy as np
import itertools
def calculate_distance(c1, c2):
return float(np.linalg.norm(np.array(c1) - np.array(c2)))
def total_route_distance(route, dist):
total = sum(dist[route[i], route[i + 1]] for i in range(len(route) - 1))
total += dist[route[-1], route[0]] # close the loop
return total
def brute_force_tsp(coords, names):
n = len(coords)
dist = np.zeros((n, n))
for i in range(n):
for j in range(i, n):
dist[i, j] = dist[j, i] = calculate_distance(coords[i], coords[j])
best_d = float('inf')
best_r = None
for r in itertools.permutations(range(n)):
d = total_route_distance(r, dist)
if d < best_d:
best_d = d
best_r = r
return [names[i] for i in best_r], best_d
# 10 Indian cities
city_names = ['Mumbai', 'Delhi', 'Bangalore', 'Hyderabad', 'Ahmedabad',
'Chennai', 'Kolkata', 'Surat', 'Pune', 'Jaipur']
np.random.seed(0)
coords = [(int(np.random.randint(1, 100)),
int(np.random.randint(1, 100))) for _ in city_names]
best_route, best_distance = brute_force_tsp(coords, city_names)
print('Best route:', best_route)
print('Best distance: {:.4f}'.format(best_distance))
On the same ten Indian cities, the script returns a tour of about 230 units:
Output
Best route: ['Mumbai', 'Ahmedabad', 'Pune', 'Jaipur', 'Chennai', 'Delhi', 'Surat', 'Hyderabad', 'Kolkata', 'Bangalore']
Best distance: 229.5617
Two refinements matter in working code.
Fix the start city to 0 and permute the remaining n-1 cities. Rotations of the same loop are equivalent, so dividing the work by n saves genuine time.
Prune with a running lower bound: keep the best distance seen so far and skip permutations whose partial sum already exceeds it.
Greedy vs. brute force
Use greedy when n is large or when you need an answer now. Use brute force when n is small enough that the loop completes in seconds, and you want a number to compare against.
The 4-city greedy result above is 21.69, and the brute-force optimum is 21.69. For n=4 greedy happens to be optimal. On the 10-city case the gap is wider, and the percentage tells you how far greedy is from the truth on your specific input.
Conclusion
TSP in Python is approachable from day one. Nearest-neighbour greedy gives you a fast upper bound. Brute force gives you the true optimum up to about ten cities.
The next step, once you have outgrown both, is the Held-Karp dynamic programming algorithm in O(n² · 2ⁿ), which fits n up to 20 on a workstation. Beyond that, OR-Tools and Lin-Kernighan heuristics are the next stop.
What is the travelling salesman problem in one sentence?
Given a list of cities and the distance between every pair, find the shortest loop that visits each city once and returns to the start. It is NP-hard in general.
Is the greedy algorithm always optimal?
No. Greedy makes a locally optimal choice at every step and can miss the global optimum. On random inputs it typically lands 10% to 25% above the brute-force optimum.
When is brute force practical?
Brute force is practical for n up to about 10 or 11. At n=10 the work is 9! = 362,880 permutations. At n=15 the work is 14! = 87 billion and the loop is no longer a practical option.
What algorithm should I use for n between 12 and 20?
Held-Karp dynamic programming in O(n² · 2ⁿ) gives the exact answer and runs in seconds for n up to 20. Beyond that, switch to OR-Tools or a Lin-Kernighan heuristic.

