Tossing potatoes

December 2, 2025

Tonight I have prepared sautéed potatoes. It's pretty simple: cut the potatoes in rough cubes, ideally of the same size, and throw them in a pan that contains enough oil (or better, duck fat). Then you just need to sauté them while they cook on a relatively high heat. Salt, pepper, and there you have it, crispy sautéed potatoes.

As I was cooking my potatoes, I asked myself: how many times should I toss them?

Let's try to estimate this. We have potatoes with four sides. Every time I toss the potatoes, each cube lands randomly on one of its sides with equal probabilities, assuming my tossing technique is good enough. We'll assume that a potato cube is well cooked only after each of its four sides has been against the pan.

Let's first look at a single potato cube: what's the probability that it is well cooked after tosses? It's a combinatorial problem, let's break it down:

  1. There are possible sequences.
  2. There are possible sequences that have never touched face . The same applies to face 2, 3, and 4.
  3. is more than the number of sequences that miss at least one side: for example, sequences where faces 1 and 2 are both missing will be counted twice. Thus, we need to subtract the number of sequences where two faces were missing. There are possible missing doublets, and each of them have possible sequences. Therefore we remove of these double counted sequences. Finally, removing this number, we have removed cases where 3 faces were missing from the whole sequence. The first term counted them 3 times, while the second term counts them also 3 times, thus we need to add them back to count them exactly once.

The final result is This follows the inclusion-exclusion principle.

Of course potato cubes have 6 faces, not 4, oops. Luckily, this gave us an intuition of the inclusion-exclusion principle. The generalization of the above result for faces is given by:

The probability tends exponentially to 1 as increases, as illustrated in the following figure:

Probability that a potato has touched all 6 sides after t tosses.

Probability that a potato has touched all 6 sides after t tosses.

As expected, under 6 tosses, the probability that the potato is well cooked is zero, because it cannot have been on 6 faces. The figure shows that after 27 tosses, there is already more than 95% probability that the potato cube has visited all 6 faces. I don't have only one potato piece, but of them (say 40). The number of well cooked potatoes follows a binomial distribution, with success probability .

Median number of well cooked potatoes out of \(N=40\), against the number of tosses. Error bars indicate the 5 to 95% quantiles.

Median number of well cooked potatoes out of \(N=40\), against the number of tosses. Error bars indicate the 5 to 95% quantiles.

In my pan, most of the 40 potatoes will be well cooked after about 30 tosses. It's hard work, but totally worth it.

Of course many of the approximations I made are impractical, and this shouldn't serve as a 3 Michelin star level cooking guide. But this little exercise made me discover the inclusion-exclusion principle, and next time I'll count how many tosses I naturally do.

The figures were produced with these two scripts:

Show code
#!/usr/bin/env python

import argparse
import matplotlib.pyplot as plt
import math
import numpy as np

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--out', type=str, default=None, help='output plot')
    args = parser.parse_args()

    out = args.out

    m = 6 # number of faces
    t = np.arange(0, 50)

    Pt = np.zeros(len(t))
    for k in range(m+1):
        Pt += (-1)**k * math.comb(m, k) * ((m-k)/m)**t
    Pt = np.clip(Pt, 0, 1)

    for t_, Pt_ in zip(t, Pt):
        print(t_, Pt_)

    fig, ax = plt.subplots()
    ax.plot(t, Pt, '-o', clip_on=False)
    ax.set_xlabel(r'$t$')
    ax.set_ylabel(r'$P_6(t)$')
    plt.tight_layout()
    if out is None:
        plt.show()
    else:
        plt.savefig(out)


if __name__ == '__main__':
    main()
Show code
#!/usr/bin/env python

import argparse
import matplotlib.pyplot as plt
import math
import numpy as np
from scipy.stats import binom

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--out', type=str, default=None, help='output plot')
    args = parser.parse_args()

    out = args.out

    m = 6 # number of faces

    N = 40
    t = np.arange(0, 50)

    Pt = np.zeros(len(t))
    for k in range(m+1):
        Pt += (-1)**k * math.comb(m, k) * ((m-k)/m)**t
    Pt = np.clip(Pt, 0, 1)

    median = binom.median(N, Pt)
    lo = binom.ppf(0.05, N, Pt)
    hi = binom.ppf(0.95, N, Pt)

    fig, ax = plt.subplots()
    ax.errorbar(t, median, yerr=np.stack((median-lo, hi-median)), capsize=3, fmt='-o', clip_on=False)
    ax.set_xlabel(r'Number of tosses $t$')
    ax.set_ylabel(r'Number of well cooked potatoes $k$ out of $N={}$'.format(N))
    plt.tight_layout()
    if out is None:
        plt.show()
    else:
        plt.savefig(out)


if __name__ == '__main__':
    main()