Back to Blogs

Building a Real-Time MCQ Quiz System with WebSocket

Buddhadeb Koner

Buddhadeb Koner

15 Nov 2024

8 min read
WebSocketNode.jsReactReal-timeFull Stack
Building a Real-Time MCQ Quiz System with WebSocket

Recently, I built a real-time MCQ quiz system that uses WebSocket for instant communication between students and the server. The project features automatic question timing, attempt tracking, and comprehensive result analytics. Let me walk you through the architecture and implementation! 🚀

System Overview

The quiz system is built with a focus on real-time communication and user experience. Here are the core features:

Core Features:

  • Real-time WebSocket communication for instant updates
  • 5 MCQ questions with 10-second timer per question
  • 3 attempts to change answer per question
  • Auto-submission on max attempts or timeout
  • Private scoring - results shown only at the end
  • Detailed performance analytics with answer breakdown

Quiz Rules & Constraints

The system enforces these rules:

  • Time Limit: 10 seconds per question
  • 🔄 Attempts: Maximum 3 answer changes per question
  • Auto-Submit: Automatic submission on 3rd selection or timeout
  • Early Submit: Manual submission allowed at any time
  • 🔒 Privacy: No immediate feedback, results shown at the end

The key challenge was managing real-time state synchronization between client and server while maintaining quiz integrity and preventing cheating.

Architecture Overview

The system follows a client-server architecture with WebSocket for bidirectional communication. Here's how the components interact:

plaintext
┌─────────────────────────────────────┐
│      FRONTEND (React + Vite)        │
├─────────────────────────────────────┤
│  • Connection Management            │
│  • Question Display                 │
│  • Timer & Attempt Counter          │
│  • Warning System                   │
│  • Results Dashboard                │
└──────────────┬──────────────────────┘
               │ WebSocket
               ▼
┌─────────────────────────────────────┐
│      BACKEND (Node.js + ws)         │
├─────────────────────────────────────┤
│  • Session Management               │
│  • Question Timer                   │
│  • Attempt Tracking                 │
│  • Answer Processing                │
│  • Results Calculation              │
└─────────────────────────────────────┘

WebSocket Message Protocol

Communication between client and server uses JSON messages with specific types. Here are the main message types:

Client → Server Messages:

javascript
// Answer Selection
{
  "type": "answer",
  "answerIndex": 2
}

// Manual Submission
{
  "type": "submit",
  "answerIndex": 1
}

// Connection Health Check
{
  "type": "ping"
}

Server → Client Messages:

javascript
// Welcome Message
{
  "type": "welcome",
  "studentId": "student_1234567890_abc123def",
  "totalQuestions": 5,
  "instructions": ["Rule 1", "Rule 2"]
}

// Question Data
{
  "type": "question",
  "questionNumber": 1,
  "question": "What does HTML stand for?",
  "options": ["A", "B", "C", "D"],
  "timeLimit": 10,
  "attemptsRemaining": 3
}

Attempt System Logic

One of the most interesting features is the 3-attempt system. Each answer selection counts as an attempt, and users can change their answer up to 3 times:

javascript
// Backend: Handle Answer Selection
function handleAnswer(session, answerIndex) {
  session.currentAttempts++;
  session.selectedAnswer = answerIndex;
  
  const remaining = session.maxAttempts - session.currentAttempts;
  
  if (remaining === 0) {
    // Auto-submit on 3rd attempt
    processAnswer(session, answerIndex);
  } else {
    // Send attempt warning
    session.ws.send(JSON.stringify({
      type: "attempt-warning",
      message: `Answer selected! ${remaining} attempts remaining...`,
      attemptsRemaining: remaining
    }));
  }
}

Question Timer Implementation

Each question has a 10-second timer that runs on the server. If the timer expires, the question is automatically marked as timed out:

javascript
// Start question timer
function startQuestionTimer(session) {
  clearTimeout(session.questionTimer);
  
  session.questionTimer = setTimeout(() => {
    if (!session.hasAnswered) {
      handleTimeOut(session);
    }
  }, session.questionTimeLimit * 1000);
}

// Handle timeout
function handleTimeOut(session) {
  session.answers.push({
    questionId: session.currentQuestionIndex,
    selectedAnswer: null,
    isCorrect: false,
    timedOut: true,
    timeTaken: session.questionTimeLimit
  });
  
  sendFeedbackAndNextQuestion(session);
}

Session Management

Each student connection creates a quiz session that tracks their progress:

javascript
// Quiz Session Structure
const session = {
  ws: WebSocketConnection,
  studentId: `student_${Date.now()}_${randomString}`,
  currentQuestionIndex: 0,
  answers: [],
  score: 0,
  startTime: new Date(),
  questionTimer: null,
  currentAttempts: 0,
  maxAttempts: 3,
  questionTimeLimit: 10,
  hasAnswered: false
};

Results & Analytics

At the end of the quiz, students receive detailed analytics including their score, time taken, and a question-by-question breakdown:

javascript
// Final Results Message
{
  "type": "results",
  "score": 4,
  "totalQuestions": 5,
  "percentage": 80,
  "totalTime": 45,
  "answerBreakdown": [
    {
      "questionNumber": 1,
      "question": "What does HTML stand for?",
      "yourAnswer": "Hyper Text Markup Language",
      "correctAnswer": "Hyper Text Markup Language",
      "isCorrect": true,
      "attempts": 1,
      "timeTaken": 5
    }
  ],
  "summary": {
    "correct": 4,
    "incorrect": 1,
    "timeouts": 0,
    "maxAttemptsReached": 1
  }
}

Frontend Implementation

The React frontend manages WebSocket connection, question state, and UI updates:

javascript
// React: WebSocket Connection
const [ws, setWs] = useState(null);
const [question, setQuestion] = useState(null);
const [timer, setTimer] = useState(10);
const [attempts, setAttempts] = useState(3);

useEffect(() => {
  const websocket = new WebSocket("ws://localhost:3000");
  
  websocket.onmessage = (event) => {
    const data = JSON.parse(event.data);
    
    switch(data.type) {
      case "question":
        setQuestion(data);
        setTimer(data.timeLimit);
        setAttempts(data.attemptsRemaining);
        break;
      case "attempt-warning":
        setAttempts(data.attemptsRemaining);
        break;
      case "results":
        showResults(data);
        break;
    }
  };
  
  setWs(websocket);
}, []);

Technology Stack

Technologies used in this project:

  • Backend: Node.js + Express + ws (WebSocket library)
  • Frontend: React + Vite + Tailwind CSS
  • Communication: WebSocket for real-time bidirectional updates
  • Architecture: Functional programming approach for maintainability

Project Structure

plaintext
student-server/
├── backend/
│   ├── server.js
│   ├── package.json
│   └── src/
│       ├── controllers/
│       │   └── controller.js
│       └── routes/
│           └── socket.route.js
└── frontend/
    ├── src/
    │   ├── App.jsx
    │   ├── main.jsx
    │   └── index.css
    ├── package.json
    └── .env

Key Challenges & Solutions

Problems solved during development:

  • State Synchronization - Used WebSocket for instant state updates between client/server
  • Timer Accuracy - Server-side timers prevent client-side manipulation
  • Connection Handling - Automatic cleanup on disconnect prevents memory leaks
  • Privacy Protection - Generic feedback messages prevent answer leaking

The functional programming approach made the codebase more maintainable and testable, with pure functions handling each aspect of the quiz flow.

Security Features

Security measures implemented:

  • CORS configuration for cross-origin requests
  • Input validation on all WebSocket messages
  • Session isolation - each connection is independent
  • Server-side timing - prevents client manipulation
  • Error handling with graceful degradation

Future Enhancements

Some features I'm planning to add in future versions:

Planned improvements:

  • User authentication and progress tracking
  • Database integration for question storage
  • Leaderboard and competitive mode
  • Question difficulty levels
  • Topic-based quiz categories
  • Mobile app with React Native

Lessons Learned

Building this project taught me valuable lessons about real-time systems, state management, and user experience design. The most important takeaway was understanding how to balance functionality with simplicity in the user interface.

Real-time applications require careful consideration of network latency, connection handling, and state synchronization. WebSocket provides the perfect tool for this use case!

If you're interested in building real-time applications, I highly recommend starting with WebSocket. It's powerful, efficient, and opens up endless possibilities for interactive web experiences. Happy coding! 💻

Buddhadeb Koner | FullStack Web Developer