Districts and Gerrymandering

How Voting Systems Work

Chapter 8 ยท Districts and Gerrymandering

How Voting Systems Work 7 shared a fixed number of seats between regions. This chapter asks the follow-up question: once a region is entitled to several seats and elects them from single-member districts, who decides where the district lines go? The answer matters more than most people expect, because the same voters can produce very different results depending on how they are grouped. Drawing lines to tilt the result on purpose is called gerrymandering. The maths stays at counting, percentages and one area-over-perimeter formula; the Python does the bookkeeping and, in the last example, tries every possible map.

What was run, and what to trust
Every Python snippet in this chapter and in the three exercise solutions was executed, and the output in the comments is what Python printed. Historical and technical claims come from two web searches whose results were encyclopedia mirrors, a law-school summary, news and blog pieces, and an arXiv paper; fetched pages are often truncated or model-summarised, so treat the wording as a paraphrase. The sources conflict on who published the famous 1812 cartoon (one names the Boston Gazette, another the Boston Centinel and the artist Elkanah Tisdale), and they normalise the efficiency gap slightly differently (by the district’s votes or by all votes in the election). All vote counts and shapes in the code are invented, not real elections or real maps. Statements marked (check) were not confirmed against a source.

Why Lines Matter

Under plurality voting in single-member districts (How Voting Systems Work 2), each district is a separate little election, and only the winner counts. Votes for a loser, and votes for a winner beyond what was needed, change nothing. A mapmaker who knows roughly where each party’s voters live can therefore choose lines that make one party’s votes count for more than the other’s. Two techniques do almost all of the work:

  • Packing: cram the opponent’s voters into a few districts they win by huge margins, wasting their surplus.
  • Cracking: split the opponent’s voters across many districts so that they are a narrow minority in each and win none.

The word itself dates from 1812 in Massachusetts. The sources I found agree it was coined in connection with state senate districts redrawn under Governor Elbridge Gerry, and that one district was said to look like a salamander, giving “Gerry-mander”. They also note the coiner may never be firmly established. The practice is older than the word and has not gone away.

Counting Wasted Votes

A wasted vote is any vote that did not help elect anyone: every vote for the loser in a district, plus the winner’s votes beyond a bare majority. The efficiency gap, proposed by Stephanopoulos and McGhee (per the sources I found), compares the two parties’ wasted votes: take party A’s wasted total minus party B’s, and divide by the total votes cast. If A wastes more than B, the map favours B. The same sources report that its authors suggested a gap of about 8% or more as a flag for possible partisan gerrymandering; I could not confirm the exact threshold in a primary source (check).

The experiment below uses fifty invented voters in five districts of ten, with party A holding 26 votes (52%) in every plan. Only the lines change.

# Illustrative numbers: 50 voters in 5 equal districts of 10; party A has 26 votes in total (52%). # Each plan lists A's votes in each district; B gets the rest. def wasted(a, b): """Votes wasted in one district: all of the loser's votes, and the winner's votes above a bare majority.""" needed = (a + b) // 2 + 1 return (a - needed, b) if a > b else (a, b - needed) def efficiency_gap(districts): """(wasted A - wasted B) / total votes. Positive means A wastes more, so the map favours B.""" wa = wb = total = 0 for a, b in districts: x, y = wasted(a, b) wa, wb, total = wa + x, wb + y, total + a + b return (wa - wb) / total plans = { "even spread": [6, 6, 6, 4, 4], "B packed": [6, 6, 6, 6, 2], "A packed": [9, 9, 3, 3, 2], } for name, a_votes in plans.items(): districts = [(a, 10 - a) for a in a_votes] seats = sum(1 for a, b in districts if a > b) print(f"{name:<12} A votes {sum(a_votes)}/50, A seats {seats}/5, efficiency gap {efficiency_gap(districts):+.2f}") # even spread A votes 26/50, A seats 3/5, efficiency gap -0.08 # B packed A votes 26/50, A seats 4/5, efficiency gap -0.32 # A packed A votes 26/50, A seats 2/5, efficiency gap +0.16

Look at the second row of the output: A wins 4 of 5 seats with 52% of the votes, because B’s 24 voters are packed into one district (8 B to 2 A) and A holds a comfortable margin elsewhere. In the third row the roles flip: A’s voters are packed into two districts (9 to 1 each) and cracked across the other three (3, 3 and 2 out of 10), so A wins only 2 of 5 seats with the majority of votes. The efficiency gap moves with the outcome: negative when the map favours A, positive when it favours B.

Plan (A has 26 of 50 votes)A votes per districtA seatsEfficiency gap
Even spread6, 6, 6, 4, 43 of 5−0.08 (mild tilt to A)
B packed6, 6, 6, 6, 24 of 5−0.32 (strong tilt to A)
A packed and cracked9, 9, 3, 3, 22 of 5+0.16 (tilt to B)

Measuring Shape: Compactness

A second family of tests ignores votes and looks at the shape of each district. A fair-looking district is usually blob-like; a tentacled one invites suspicion. The Polsby–Popper score, introduced in 1991 according to the sources I found, is 4π × area ÷ perimeter². A perfect circle scores 1 and thin or ragged shapes score near 0. Real maps measure area and perimeter from boundary data; the code below uses shapes built from unit squares, where the perimeter is just the number of cell edges that face the outside.

from math import pi def area_perimeter(cells): """Area and perimeter of a shape made of unit squares given as (row, col) cells.""" cells = set(cells) perimeter = 0 for r, c in cells: for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)): if (r + dr, c + dc) not in cells: perimeter += 1 return len(cells), perimeter def polsby_popper(cells): """4 * pi * area / perimeter**2: 1 for a perfect circle, near 0 for a thin or ragged shape.""" area, perimeter = area_perimeter(cells) return 4 * pi * area / perimeter ** 2 # Illustrative shapes, all 16 cells in area. shapes = { "4x4 square": [(r, c) for r in range(4) for c in range(4)], "2x8 block": [(r, c) for r in range(2) for c in range(8)], "1x16 strip": [(0, c) for c in range(16)], "plus + arms": [(r, 3) for r in range(7)] + [(3, c) for c in range(7) if c != 3] + [(1, 1), (1, 5), (5, 1)], } for name, cells in shapes.items(): area, perimeter = area_perimeter(cells) print(f"{name:<12} area {area:>2} perimeter {perimeter:>2} Polsby-Popper {polsby_popper(cells):.3f}") # 4x4 square area 16 perimeter 16 Polsby-Popper 0.785 # 2x8 block area 16 perimeter 20 Polsby-Popper 0.503 # 1x16 strip area 16 perimeter 34 Polsby-Popper 0.174 # plus + arms area 16 perimeter 40 Polsby-Popper 0.126

The 4x4 square scores 0.785 (the best a grid of squares can do for this area, since a square is not a circle). The same 16 cells stretched into a 1x16 strip drop to 0.174, and the tentacled plus-shape to 0.126. Area is identical in all four, so the score is measuring perimeter, which is why compactness is easy to compute and easy to argue about: it says nothing about who the voters are, and a real coastline or river border can make a perfectly innocent district look ragged.

MeasureLooks atStrengthBlind spot
Polsby–Popperarea and perimeter of each districtsimple, needs no election datanatural borders; a compact map can still be biased
Efficiency gapwasted votes in an actual electionmeasures the outcome, not the lookdepends on that election’s turnout and swing; sources differ on its normalisation
Seats vs votesshare of seats against share of voteseasy to explainsome mismatch is normal under plurality

How Much Power Does the Mapmaker Have?

The final experiment removes all judgement: it takes a 4x4 grid of voting blocks, with party A holding 9 of the 16 (56%) clustered on the left, and tries every way to cut the grid into four connected districts of four blocks each. A district counts for A only with at least 3 of its 4 blocks; a 2-2 split goes to neither party in this simplified model. The function builds each connected piece by growing it one neighbouring cell at a time, then fills in the rest recursively.

from itertools import product SIZE, PER = 4, 4 # a 4x4 grid of voting blocks, drawn into 4 connected districts of 4 blocks each def neighbours(cell): r, c = cell return [(r + dr, c + dc) for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)) if 0 <= r + dr < SIZE and 0 <= c + dc < SIZE] def pieces_from(start, free): """Every connected set of PER free cells that contains start.""" found, frontier = set(), {frozenset([start])} for _ in range(PER - 1): frontier = {piece | {n} for piece in frontier for cell in piece for n in neighbours(cell) if n in free and n not in piece} found |= frontier return found def all_maps(free=None): """Every way to cut the grid into connected districts of equal size.""" free = frozenset(product(range(SIZE), repeat=2)) if free is None else free if not free: yield [] return start = min(free) for piece in pieces_from(start, free): for rest in all_maps(free - piece): yield [piece] + rest # Illustrative layout: 'A' blocks (9 of 16, 56%) are packed on the left, 'B' blocks (7) on the right. layout = ["AAAB", "AAAB", "AABB", "AABB"] vote = {(r, c): layout[r][c] for r in range(SIZE) for c in range(SIZE)} tally = {} maps = list(all_maps()) for plan in maps: a_seats = sum(1 for d in plan if sum(vote[c] == "A" for c in d) > PER // 2) tally[a_seats] = tally.get(a_seats, 0) + 1 print("valid maps:", len(maps)) for seats in sorted(tally): print(f" A wins {seats} of 4 seats in {tally[seats]} maps") # valid maps: 117 # A wins 1 of 4 seats in 7 maps # A wins 2 of 4 seats in 84 maps # A wins 3 of 4 seats in 26 maps

There are 117 valid maps. Proportional shares would give A about 2.25 seats, and 84 of the maps give exactly 2. But 7 maps hand A only 1 seat and 26 give it 3: the mapmaker’s choice spans a range, and a mapmaker who wants a particular outcome simply picks from the end of the list that suits them. This is the heart of the problem. Nothing in the layout of voters forces one answer; the lines are a free choice, and free choices can be made with an agenda. Exercise 3 repeats the experiment on random layouts.

What the Law and the Maths Say

In the United States, the sources I found report that the Supreme Court held in Rucho v. Common Cause (2019), by a 5-4 majority, that federal courts cannot decide partisan gerrymandering claims because they are political questions, leaving the matter to states; racial gerrymandering claims remain actionable. One result I found was a blog post describing a later decision, which I could not confirm against a second source, so I leave it out. Other countries handle the problem differently: some give boundary drawing to independent commissions (check which, and when), and systems with proportional representation (How Voting Systems Work 7 covered the seat-sharing step) largely avoid the problem because seats follow votes across whole regions rather than single districts (check).

A paper on arXiv titled “An impossibility theorem for gerrymandering” argues, as the search summary put it, that districting cannot always satisfy all three of one person one vote, Polsby–Popper compactness and a low efficiency gap at once. I did not read the paper’s full argument, so take that as a pointer rather than a result I verified (check). It echoes the theme of How Voting Systems Work 6: every fairness measure is a value choice, and no one number settles the question.

Common mistakes
Reading a high efficiency gap as proof of intent (geography can cause it: voters who cluster in cities get packed with no mapmaker needed); trusting compactness alone (an ugly shape can be harmless and a tidy one biased); forgetting that the gap depends on one election’s votes, not a map’s whole life; and mixing up the sign convention. Here a positive gap means party A wasted more, so the map favours B; some authors use the opposite sign, so always check which way a quoted figure points.
Exercise 1

Six invented districts have the vote tallies A:B of 5200:4800, 4100:5900, 3300:6700, 6100:3900, 4600:5400 and 4400:5600. Print each district’s wasted votes for both parties, then the efficiency gap, which party it favours, and whether it passes an 8% flag.

๐Ÿ“„ View solution
Exercise 2

Build three 12-cell shapes (a 3x4 block, an L shape and a staircase) and rank them by Polsby–Popper score. Do any two tie? What does that tell you about the score?

๐Ÿ“„ View solution
Exercise 3

Place 9 A blocks at random (seeded) on the 4x4 grid for eight layouts, enumerate all 117 maps for each, and report the range of seats A can win. Print the layout where the mapmaker has the widest range.

๐Ÿ“„ View solution

Quick Reference

  • Packing: concentrate the opponent’s voters in a few districts
  • Cracking: split the opponent’s voters so they lose everywhere narrowly
  • Wasted votes: all of the loser’s votes, plus the winner’s votes beyond a bare majority
  • Efficiency gap: (wasted A − wasted B) ÷ total votes; positive favours B in this chapter’s convention
  • Polsby–Popper: 4π × area ÷ perimeter²; 1 is a circle, near 0 is thin or ragged
  • Mapmaker’s power: many valid maps, many possible seat counts
  • Always state: the election used, the sign convention and the normalisation when quoting a gap
Coming next
How Voting Systems Work 9 is about power and weighted votes: voting weight versus power, and the Shapley–Shubik and Banzhaf indices computed in Python.