3Blue1Brown recently posed a puzzle of the month ( You can check it here: https://youtube.com/shorts/wGffBCfrAsE ), where the task is to find the number of intersections between 100 random chords on a circle. Let’s solve it!
Solution for 2 pairs of chords
First, let’s try to calculate the probability of two chords intersecting. To make it easier, let’s draw it.

so as you can see, giving the chord AB, it splits the circle into 2 parts, which give us 3 use cases when dropping a new chord CD. Either both C and D lie on the top arc, in this case, there is not intersection, or they both lie on the bottom arc, in this case, there is not intersection too, and the final option is that one of them lies on the top arc and the other lies on the bottom one, which gives us 1 intersection. so based on the image, the probablity of 2 chords crossing each other is 1/3 right? It is tempting to look at those three cases and assume that because there are three options, the chance of a crossing is exactly 1/3. But in probability, outcomes aren’t always equally weighted! To confidently prove this, we need to measure exactly how likely that third scenario actually is.
Let’s elegantly prove it mathematically.
Anchor the Moving Targets
Dealing with four totally random points is chaotic. But because a circle is perfectly symmetrical, the absolute position of the first point doesn’t matter. We can lock point A at the very top of the circle (let’s call it angle 0).
Now, point B is our only moving target for the first chord. Let’s say it lands at some random angle (which can be anywhere from to ).
Measure the Arcs
Chord AB has now split our circle into two distinct arcs. If we want to drop a new point onto the circle, the probability of it landing on a specific arc is just the length of that arc divided by the total circumference.
Working in radians, the total circumference is . This makes the probabilities beautifully simple:
Probability of landing on Arc 1:
Probability of landing on Arc 2:
The Probability of a Crossing (for a fixed chord)
For our second chord (CD) to cross AB, point C must land on one arc, and point D must land on the other.
Since C could land on Arc 1 and D on Arc 2—or D could land on Arc 1 and C on Arc 2—we multiply the probabilities of those two independent events and double it to account for both orderings:
The Grand Average
That formula gives us the chance of an intersection if point B is frozen at a specific angle . But B is totally random! To find the true, overall probability, we have to find the average value of this function across every possible angle from to .
In calculus, we do this by integrating our function over the interval and dividing by the length of that interval ():
If we expand the polynomial and pull out the constants, it looks like this:
Now, apply the power rule to integrate, evaluate from to , and watch the magic happen:
The math flawlessly backs up our intuition. For any two random chords drawn on a circle, there is exactly a 1 in 3 chance that they will intersect!
Scaling Up to 100 Chords
Now for the real challenge: what happens when we drop 100 random chords onto the circle? What is the expected number of total intersections?
When faced with a tangled web of 100 lines, it is tempting to panic. My first intuition was that we shouldn’t try to calculate complex overlaps. Instead, if we know the probability of a single pair intersecting, we can just figure out how many possible pairs exist in a group of 100 chords, and multiply that total by our 1/3 chance.
It feels like a clean, logical shortcut, but is it mathematically valid?
The answer is yes, and it is entirely thanks to the Linearity of Expectation.
The Magic of Linearity of Expectation
In probability, the Linearity of Expectation states that the expected value of a sum of random events is simply the sum of their individual expected values.
Here is the most mind-blowing part of this theorem: it works even if the events are dependent on each other.
If you look at three chords (A, B, and C), the event of A crossing B is not strictly independent of A crossing C. In a normal probability problem, this dependency would make the math an absolute nightmare. But Linearity of Expectation gives us permission to completely ignore the overlapping chaos. We can treat every single pair of lines as if it exists in a vacuum.
The Final Calculation
To solve the puzzle, we just need to follow two simple steps:
- Count the pairs: How many distinct pairs of lines can we make out of 100 chords? This is a classic combinatorics problem (“100 choose 2”).
- Multiply by the expected value: We know from our integral that the expected number of intersections for any individual pair is exactly .
By applying the Linearity of Expectation, we simply multiply the total number of unique pairs by the expected value of a single pair:
And there we have it! If you drop 100 random chords onto a circle, you can expect exactly 1,650 intersections. By starting with a single pair of lines and relying on the Linearity of Expectation, we bypassed a mathematically impossible web of dependencies and solved the whole thing with basic arithmetic.
PS: by running the following code ( Thank you claude :) ) we can simulate the results.
import random
import math
def count_intersections(n_chords):
# Generate n_chords random chords, each defined by 2 random angles on the circle
chords = []
for _ in range(n_chords):
a = random.uniform(0, 2 * math.pi)
b = random.uniform(0, 2 * math.pi)
chords.append((a, b))
def crosses(c1, c2):
a, b = c1
c, d = c2
def in_arc(x, start, end):
# is x within the arc going counterclockwise from start to end?
if start < end:
return start < x < end
else:
return x > start or x < end
# Two chords (a,b) and (c,d) cross iff exactly one of c,d is inside arc(a,b)
c_in = in_arc(c, a, b)
d_in = in_arc(d, a, b)
return c_in != d_in
count = 0
for i in range(len(chords)):
for j in range(i + 1, len(chords)):
if crosses(chords[i], chords[j]):
count += 1
return count
def run_trials(n_chords, n_trials):
total = 0
results = []
for _ in range(n_trials):
c = count_intersections(n_chords)
results.append(c)
total += c
avg = total / n_trials
return avg, results
if __name__ == "__main__":
n_chords = 100
n_trials = 20000
avg, results = run_trials(n_chords, n_trials)
expected = math.comb(n_chords, 2) / 3
print(f"Chords per trial: {n_chords}")
print(f"Number of trials: {n_trials}")
print(f"Theoretical expected intersections: {expected}")
print(f"Simulated average intersections: {avg:.2f}")
print(f"Difference: {abs(avg - expected):.2f} ({100 * abs(avg - expected) / expected:.2f}%)")
results.sort()
print(f"Min: {results[0]}, Max: {results[-1]}, Median: {results[len(results)//2]}")
Here is the output of the script:
Chords per trial: 100
Number of trials: 20000
Theoretical expected intersections: 1650.0
Simulated average intersections: 1649.37
Difference: 0.63 (0.04%)
Min: 1052, Max: 2233, Median: 1646