Por que sortear times com array.sort(() => Math.random() - 0.5) não é justo?
Porque o algoritmo de ordenação espera um comparador consistente, e essa função devolve resultado diferente a cada chamada para o mesmo par. O motor então produz permutações enviesadas, algumas ordens de saída ficam muito mais prováveis que outras, mesmo com Math.random sorteando de forma justa por dentro.
O sorteio de times usa números aleatórios seguros?
Sim, o embaralhamento Fisher-Yates sorteia cada troca de posição com crypto.getRandomValues, a API criptográfica do navegador, em vez de Math.random. Isso não muda a justiça da distribuição em si, mas fecha a porta para quem tentasse prever ou reproduzir o sorteio a partir de uma semente fraca.
Por que pedir times de tamanho 4 pode gerar times de tamanho 3?
Porque o modo "por tamanho" usa o valor pedido só para calcular quantos times criar, arredondando para cima. Com 9 pessoas e tamanho-alvo 4, o cálculo fecha em 3 times, e o round-robin distribui as 9 pessoas igualmente entre esses 3 times, resultando em 3 integrantes por time.
Times sempre saem com o mesmo número de integrantes?
Só quando o total de pessoas é múltiplo do número de times. Quando não é, o round-robin espalha o resto da divisão entre os primeiros times sorteados, e a diferença de tamanho entre o time maior e o menor nunca passa de 1 integrante.