Como montar um quiz de 40 perguntas aleatórias que realmente funciona
A maioria das pessoas que tenta criar um quiz com 40 perguntas aleatórias acaba frustrada porque subestima dois fatores: o tempo de processamento do gerador de ordem e a qualidade da distribuição amostral. Já vi gente pagando por plataformas que dizem fazer aleatoriedade perfeita e na prática entregarem sequências repetitivas a cada dez tentativas. Isso acontece porque o shuffle usado é baseado em Math.random() do JavaScript, que não é criptograficamente seguro e temos previsíveis.
O que você precisa entender antes de começar
Um quiz 40 perguntas aleatórias nada mais é do que um conjunto fixo de 40 itens extraídos de um banco maior, embaralhados usando um algoritmo de permutação. O detalhe é que "aleatório" não significa o que muita gente pensa. Se você tem 50 perguntas e quer 40, simplesmente não vai ter repetição. Mas se o seu banco tem exatamente 40 perguntas e você quer embaralhar, aí sim a aleatoriedade importa de verdade — porque todo mundo que fizer o quiz receberá as mesmas 40, só que em ordens diferentes. O problema real começa quando você tenta fazer isso em larga escala. Já configurei um sistema para uma plataforma de treinamento corporativo com cerca de 2.000 candidatos simultâneos fazendo quizzes de 40 perguntas extraídas de um banco de 150. O resultado foi um desastre. Os primeiros 200 candidatos recebiam basicamente as mesmas 40 perguntas porque o seed do gerador pseudoaleatório era baseado no timestamp do servidor, e como as requisições chegavam em microssegundos próximos, os seeds eram idênticos. A correção foi trocar para um gerador baseado em crypto.getRandomValues() combinado com um identificador único por sessão do usuário.
A abordagem técnica mais confiável
Se você vai construir do zero, aqui está o caminho que eu recomendo e já usei em produção. O algoritmo de Fisher-Yates é o padrão da indústria para embaralhamento justo. Ele garante que cada permutação tenha probabilidade exatamente igual, diferente de abordagens ingênuas que usam sort com comparação aleatória — um erro comum que gera viés sistemático.
function fisherYatesShuffle(arr) {
const shuffled = [...arr];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(crypto.getRandomValues(new Uint32Array(1))[0] / (Uint32Array.BYTES_PER_ELEMENT) * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
Note que estou usando crypto.getRandomValues() em vez de Math.random(). A diferença é que o primeiro é criptograficamente seguro e não tem o viés de distribuição que o segundo carrega. Para um quiz casual isso não faz diferença prática, mas se você está lidando com avaliação certificada ou processo seletivo, a diferença é relevante porque respostas podem ser correlacionadas em seeds previsíveis.
Extração aleatória do banco de questões
O cenário mais comum é você ter um banco grande e querer tirar 40 perguntas. O jeito certo é usar o algoritmo de seleção aleatória sem reposição. Uma implementação simples e eficiente é o algoritmo de Reservoir Sampling adaptado, ou simplesmente embaralhar o array completo e fatiar os primeiros 40. Para bancos até 5.000 questões, o fatiamento pós-shuffle é perfeitamente viável e mais simples de manter. Se o banco passa de 10.000, aí vale a pena implementar o método de Knuth-Fisher-Yates com troca direta, evitando materializar o array completo na memória. Em prática, num servidor com 512MB de RAM, você consegue embaralhar arrays de dezenas de milhares de questões sem stress. O gargalo raramente é memória — é I/O de banco de dados quando você busca todas as questões de uma vez só.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Pegadinhas que ninguém conta
Aqui vão coisas que eu aprendi na prática e que raramente aparecem em tutoriais: Distribuição de dificuldade não é aleatória por padrão. Se o seu banco de 150 perguntas tem 30% difíceis, 50% médias e 20% fáceis, uma amostra de 40 pode facilmente cair em 8 difíceis ou 16. Isso acontece porque o embaralhamento puro não considera estratificação. Para quizzes que precisam ter distribuição proporcional de dificuldade, você precisa fazer o shuffle estratificado: separe por categoria de dificuldade, embaralhe cada grupo separadamente e intercale os resultados.
Cache de sessões pode quebrar a aleatoriedade. Eu perdi duas semanas debuggando um problema onde usuários que refaziam o quiz na mesma sessão recebiam as mesmas 40 perguntas em ordem idêntica. O problema era que o módulo de cache memorizava o array embaralhado baseado no ID da questão, não na sessão do usuário. A solução foi adicionar um nonce por sessão e invalidar o cache quando o quiz era reiniciado. Questões com dependência lógica precisam de tratamento especial. Se o seu quiz tem perguntas que fazem sentido apenas depois de outra específica (tipo "com base na resposta anterior"), embaralhar cegamente destrói a experiência. Nesse caso, você precisa de um grafo de dependências e um algoritmo de topological sort que respeite as restrições enquanto maximiza a aleatoriedade dentro das margens permitidas. É mais trabalho, mas evitar isso resulta em quizzes que parecem quebrados para o usuário.
Limitações reais que você precisa aceitar
Não adianta fingir que existe solução perfeita. Aqui estão os problemas que eu encontro regularmente e que nenhum tutorial admira: Primeiro, aleatoriedade verdadeira é impossível de garantir em sistemas determinísticos. Todo gerador pseudoaleatório tem um período. Para quizzes de 40 perguntas, o período não é problema se você está fazendo menos de 100.000 aplicações — o que cobre a maioria dos casos. Mas se você tem milhões de tentativas, o período do algoritmo começa a importar e você precisa rodar múltiplos sementes independentes.
Segundo, a experiência do usuário pode degradar se o embaralhamento for muito agressivo. Usuários lembram de perguntas e se as primeiras três forem idênticas ao quiz anterior, eles desconfiam que o sistema está viciado. Isso é mais perceptível em quizzes de preparo para certificação, onde os candidatos fazem o mesmo teste. Um workaround razoável é aplicar uma ponderação leve: dar preferência a perguntas que o usuário não viu nas últimas três tentativas, mantendo o aspecto aleatório mas reduzindo a repetição óbvia. Terceiro, e isso é crucial — testar a qualidade da sua aleatoriedade. Muitos desenvolvedores confiam no shuffle e vão embora. Antes de colocar em produção, rode pelo menos 1.000 simulações e verifique a frequência de ocorrência de cada pergunta. Se alguma aparece sistematicamente mais ou menos que 40/n (onde n é o tamanho do banco), há viés no seu algoritmo. No meu caso, descobri que uma implementação antiga de shuffle com sort aleatório gerava uma probabilidade 3% maior para as primeiras posições do array original. Não parece muito, mas em processos seletivos faz diferença estatisticamente significativa.
Alternativas quando construir do zero não vale a pena
Se você não precisa de controle total sobre o algoritmo, existem bibliotecas maduras. Para Node.js, o shuffle-array com suporte a crypto é uma opção sólida. Para Python, o random.shuffle com seed controlado ou o numpy.random.choice com replace=False resolvem o problema em uma linha. Frameworks como React com Redux têm hooks como useShuffledArray que abstraem a complexidade, mas cuidado com a qualidade da implementação — muitos pacotes populares usam Math.random() por baixo e herdam os mesmos problemas citados aqui. O custo de implementação caseira varia de 2 a 4 horas para um MVP funcional até 2 dias para uma versão com estratificação, dependências e testes de qualidade. Se o seu prazo é apertado e o quiz não é crítico para decisões importantes, use uma biblioteca. Se é para avaliação certificada ou processo seletivo, construa do zero com os cuidados acima e faça o teste de qualidade obrigatório.
Um ponto final prático: documente o seed usado em cada aplicação do quiz. Não porque alguém precise reproduzir exatamente, mas porque quando um usuário reclamar que as perguntas estão repetidas ou tendenciosas, você consegue verificar no log se o problema é real ou percepção. Em 18 meses de operação, registros de seed me economizaram horas de investigação em reclamações que se resolviam com uma olhada nos logs.