Documentation Index Fetch the complete documentation index at: https://mintlify.com/0xchriswilder/journey/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Quizzes provide knowledge checks at the end of lessons. They feature instant feedback, explanations for each answer, and configurable passing scores. Quizzes are optional but recommended for reinforcing key concepts.
Quizzes are lesson-scoped - they live inside the Lesson interface as an optional quiz field.
Quiz Data Structure
Quiz Interface
export interface Quiz {
title : string ;
description : string ;
questions : QuizQuestion [];
passingScore : number ; // Percentage (0-100)
}
export interface QuizQuestion {
id : number | string ;
question : string ;
options : string []; // Array of 2-4 answer choices
correctAnswer : number ; // Index of correct option (0-based)
explanation : string ; // Shown after answering
category ?: string ; // Optional grouping (e.g. 'fhe_concept', 'architecture')
}
Example Quiz Definition
Here’s the quiz from “Welcome to FHEVM” (Week 1, Lesson 1):
quiz : {
title : 'FHE Fundamentals Quiz' ,
description : 'Test your understanding of FHE and FHEVM basics' ,
passingScore : 70 ,
questions : [
{
id: 1 ,
question: 'What does FHE stand for?' ,
options: [
'Fully Homomorphic Encryption' ,
'Fast Hash Encryption' ,
'Federated Hash Encryption' ,
'Fully Hybrid Encryption' ,
],
correctAnswer: 0 ,
explanation: 'FHE stands for Fully Homomorphic Encryption, which allows computation on encrypted data without decrypting it.' ,
category: 'fhe_concept' ,
},
{
id: 2 ,
question: 'What is the main advantage of FHEVM over regular smart contracts?' ,
options: [
'Faster execution speed' ,
'Lower gas costs' ,
'Computation on encrypted data while maintaining privacy' ,
'Better user interface' ,
],
correctAnswer: 2 ,
explanation: 'FHEVM enables computation on encrypted data while maintaining privacy throughout the entire process.' ,
category: 'fhe_concept' ,
},
]
}
Questions use zero-based indexing for correctAnswer. An answer of 0 means the first option is correct.
Quiz Component Architecture
QuizSection Component
The main quiz UI is implemented in QuizSection.tsx:
src/components/lesson/QuizSection.tsx
import { QuizSection } from '@/components/lesson/QuizSection' ;
< QuizSection
quiz = { lesson . quiz }
onComplete = { ( score , passed ) => {
setQuizScore ( weekId , lessonId , score , passed );
if ( passed ) completeLesson ( weekId , lessonId );
} }
/>
Props:
quiz: Quiz - The quiz data
onComplete: (score: number, passed: boolean) => void - Callback when quiz finishes
State Management
src/components/lesson/QuizSection.tsx
const [ currentQuestion , setCurrentQuestion ] = useState ( 0 );
const [ selectedAnswer , setSelectedAnswer ] = useState < number | null >( null );
const [ showExplanation , setShowExplanation ] = useState ( false );
const [ correctCount , setCorrectCount ] = useState ( 0 );
const [ finished , setFinished ] = useState ( false );
const [ answers , setAnswers ] = useState <( number | null )[]>(
new Array ( quiz . questions . length ). fill ( null )
);
Quiz Flow
Display Question
Show question text, progress counter, and answer options as buttons.
User Selects Answer
Click handler: const handleSelectAnswer = ( answerIndex : number ) => {
setSelectedAnswer ( answerIndex );
setShowExplanation ( true );
if ( answerIndex === question . correctAnswer ) {
setCorrectCount (( c ) => c + 1 );
}
};
Show Instant Feedback
Correct answer highlighted in green
Incorrect selection highlighted in red
Explanation card displayed
“Next Question” button appears
Advance or Finish
const handleNext = () => {
if ( currentQuestion < totalQuestions - 1 ) {
setCurrentQuestion (( c ) => c + 1 );
setSelectedAnswer ( null );
setShowExplanation ( false );
} else {
setFinished ( true );
onComplete ( score , passed );
}
};
Display Results
Show final score, pass/fail status, and option to retake.
Visual Feedback
Answer Options
Options are rendered as full-width buttons with dynamic styling:
src/components/lesson/QuizSection.tsx
{ question . options . map (( option , i ) => {
let className = 'w-full justify-start text-left h-auto py-3 px-4' ;
if ( showExplanation ) {
if ( i === question . correctAnswer ) {
className += ' border-green-500 bg-green-500/10 text-green-700' ;
} else if ( i === selectedAnswer && ! isCorrect ) {
className += ' border-red-500 bg-red-500/10 text-red-700' ;
} else {
className += ' opacity-50' ;
}
}
return (
< Button
key = { i }
variant = "outline"
className = { className }
onClick = { () => handleSelectAnswer ( i ) }
disabled = { showExplanation }
>
< span className = "flex items-center gap-3" >
< span className = "h-6 w-6 rounded-full border-2 flex items-center justify-center" >
{ String . fromCharCode ( 65 + i ) } { /* A, B, C, D */ }
</ span >
< span > { option } </ span >
</ span >
</ Button >
);
})}
Explanation Card
After answering, an animated explanation appears:
src/components/lesson/QuizSection.tsx
< AnimatePresence >
{ showExplanation && (
< motion.div
initial = { { opacity: 0 , y: 10 } }
animate = { { opacity: 1 , y: 0 } }
className = { `p-4 rounded-lg border ${
isCorrect
? 'bg-green-500/5 border-green-500/20'
: 'bg-red-500/5 border-red-500/20'
} ` }
>
< div className = "flex items-start gap-2" >
{ isCorrect ? (
< CheckCircle className = "h-5 w-5 text-green-500" />
) : (
< XCircle className = "h-5 w-5 text-red-500" />
) }
< div >
< p className = "font-medium" > { isCorrect ? 'Correct!' : 'Incorrect' } </ p >
< p className = "text-sm text-muted-foreground" > { question . explanation } </ p >
</ div >
</ div >
</ motion.div >
) }
</ AnimatePresence >
Scoring
Score Calculation
src/components/lesson/QuizSection.tsx
const totalQuestions = quiz . questions . length ;
const score = Math . round (( correctCount / totalQuestions ) * 100 );
const passed = score >= quiz . passingScore ;
Results Screen
src/components/lesson/QuizSection.tsx
if ( finished ) {
return (
< Card className = { `border-2 ${
passed
? 'border-green-500/30 bg-green-500/5'
: 'border-red-500/30 bg-red-500/5'
} ` } >
< CardContent className = "p-8 text-center space-y-4" >
{ passed ? (
< CheckCircle className = "h-16 w-16 text-green-500 mx-auto" />
) : (
< XCircle className = "h-16 w-16 text-red-500 mx-auto" />
) }
< h3 className = "text-2xl font-bold" >
{ passed ? 'Quiz Passed!' : 'Quiz Not Passed' }
</ h3 >
< p className = "text-lg" >
Score: < span className = "font-bold" > { score } % </ span >
( { correctCount } / { totalQuestions } correct)
</ p >
< p className = "text-sm text-muted-foreground" >
Passing score: { quiz . passingScore } %
</ p >
< Button onClick = { handleReset } >
< RotateCcw className = "h-4 w-4 mr-2" />
{ passed ? 'Retake Quiz' : 'Try Again' }
</ Button >
</ CardContent >
</ Card >
);
}
Integration with Progress Tracking
Storing Quiz Results
const handleQuizComplete = ( score : number , passed : boolean ) => {
setQuizScore ( weekId , lessonId , score , passed );
if ( passed && ! completed ) {
completeLesson ( weekId , lessonId );
triggerConfetti ();
showCelebration (
`Quiz passed — " ${ lesson . title } "` ,
`Week ${ week . number } | Lesson ${ lessonIndex + 1 } ` ,
totalCompleted ,
getTotalLessons ()
);
}
};
Progress Store
src/state/bootcampStore.ts
setQuizScore : ( weekId : string , lessonId : string , score : number , passed : boolean ) =>
set (( state ) => {
const newProgress = { ... state . progress };
if ( newProgress [ weekId ]?. lessons [ lessonId ]) {
newProgress [ weekId ] = {
... newProgress [ weekId ],
lessons: {
... newProgress [ weekId ]. lessons ,
[lessonId]: {
... newProgress [ weekId ]. lessons [ lessonId ],
quizScore: score ,
quizPassed: passed ,
},
},
};
}
return { progress: newProgress };
}),
Quiz scores are persisted to localStorage via Zustand’s persist middleware.
Best Practices
Question Count 3-5 questions per quiz is ideal. More than 7 questions feels tedious.
Passing Score 70% is standard. Adjust based on difficulty (60% for hard quizzes, 80% for easy ones).
Explanations Always include clear explanations. They’re the most valuable part of the quiz.
Retakes Always allow retakes . The goal is learning, not gatekeeping.
Writing Good Quiz Questions
Do:
Test understanding of concepts , not memorization
Write clear, unambiguous questions
Provide explanatory feedback, not just “correct/incorrect”
Use realistic options (avoid obvious wrong answers)
Include “why” in explanations
Don’t:
Ask trick questions
Use double negatives
Make questions depend on previous answers
Write explanations that just repeat the question
Use “all of the above” or “none of the above” as lazy options
Example of a Well-Written Question
{
id : 5 ,
question : 'In FHEVM v0.9+, what is the correct decryption flow?' ,
options : [
'Client calls requestDecryption → Oracle decrypts → callback' ,
'Contract calls makePubliclyDecryptable → client calls publicDecrypt off-chain → client submits proof on-chain' ,
'Client decrypts locally without any on-chain interaction' ,
'Contract decrypts automatically when the value is read' ,
],
correctAnswer : 1 ,
explanation : 'In v0.9+, the contract marks values with makePubliclyDecryptable(), the client decrypts off-chain via the Relayer SDK, then submits the cleartext + proof back on-chain for verification with checkSignatures().' ,
category : 'access_control' ,
}
Why this works:
Tests understanding of architecture (not just definitions)
All options are plausible (require thought)
Explanation provides technical details and context
Answer can be verified by reading the lesson
Next Steps
Progress Tracking Learn how quiz scores feed into overall progress analytics
Lessons Return to lesson structure documentation