Design a Learning Management System
OOP design for an online learning platform covering course catalog, enrollment management, lesson sequencing, progress tracking, quiz engine, and certificate generation.
The Problem
Your ed-tech startup serves 50,000 students across 800 courses. The current system stores all content in a flat lessons table with a course_id foreign key, and every time a student opens a course page the backend recalculates their completion percentage by scanning every row. Last week an instructor rearranged the modules in a popular Python course, and 3,000 students lost their progress because the ordering was derived from database row IDs. A week before that, the quiz engine let a student submit answers to a quiz they had never unlocked, bumping their completion to 100% and generating a fraudulent certificate.
Learning platforms combine content management with a progression system. Courses have internal structure (modules, lessons, quizzes), students move through that structure at their own pace, and the system must track where each student stands. Get the progression logic wrong and students either skip material they need or get stuck on material they have already completed. Get the quiz engine wrong and certificates become meaningless.
Design the core classes for a learning management system that handles course creation with modules and lessons, student enrollment, sequential lesson unlocking, progress tracking, a quiz engine with multiple question types, and certificate generation upon course completion.
Requirements
Clarifying Questions
Before jumping into class design, ask questions to turn the vague prompt into a concrete specification. Cover four areas: core actions, error handling, boundaries, and future extensions.
You: "What does the course structure look like? Is it a flat list of lessons or a nested hierarchy?"
Interviewer: "A course contains ordered modules, and each module contains ordered lessons. Students must complete lessons in sequence within a module, and modules unlock in order."
Two levels of nesting: Course β Module β Lesson. Sequential unlock at both levels. That means we need explicit ordering on both modules and lessons, not just database insertion order.
You: "What types of lessons are there? Just video, or text and quizzes too?"
Interviewer: "Three types: video lessons, text lessons, and quiz lessons. Video and text lessons are marked complete when the student finishes them. Quiz lessons require a passing score."
Three distinct lesson types with different completion criteria. This is a strong signal for polymorphism or a strategy pattern: the completion check varies by lesson type.
You: "How does the quiz engine work? What question types do we need?"
Interviewer: "Support multiple choice, true/false, and free-text questions. Each quiz has a passing threshold as a percentage. The system auto-grades multiple choice and true/false. Free-text is graded by keyword matching for now."
Three question types with different grading logic. Another clear polymorphism signal. The passing threshold lives on the quiz, not on individual questions.
You: "What should happen if a student tries to access a lesson they haven't unlocked?"
Interviewer: "Reject the attempt. Students must complete the previous lesson before accessing the next one. Same for modules: all lessons in module N must be complete before module N+1 unlocks."
Strict sequential access. We need a method that checks whether a given lesson is accessible for a given student. This is the core progression logic.
You: "When does a student earn a certificate? Can they retake quizzes?"
Interviewer: "A certificate is generated when every lesson in every module is marked complete. Students can retake quizzes unlimited times. Only the latest attempt counts."
Certificate on full completion. Quiz retakes mean we store the latest attempt, not a history. That simplifies the data model considerably.
You: "Do courses have prerequisites? Can a student enroll in any course?"
Interviewer: "Yes, courses can have prerequisite courses. A student must have completed all prerequisites before enrolling. Some courses have no prerequisites."
Course-level prerequisites add a dependency graph. We need to check completion status of prerequisite courses before allowing enrollment.
You: "Should we handle instructor dashboards, ratings, refunds, or downloadable content?"
Interviewer: "Those are extensions. Focus on the core: course structure, enrollment, progress tracking, quizzes, and certificates. We can discuss instructor analytics and ratings as follow-ups."
Good. You have scoped out ratings, refunds, downloads, and instructor dashboards. The core is tight: structure, enrollment, progress, quizzes, certificates.
Final Requirements
Functional Requirements:
- Instructors create courses with ordered modules, each containing ordered lessons (video, text, or quiz)
- Students enroll in courses after completing all prerequisite courses
- Lessons unlock sequentially within a module; modules unlock sequentially within a course
- Progress is tracked per lesson, per module (percentage), and per course (percentage)
- Quiz lessons contain multiple question types (MCQ, true/false, free-text) with auto-grading
- Quizzes require a configurable passing score; students can retake unlimited times
- A certificate is generated when a student completes all lessons in all modules of a course
Non-Functional Requirements:
- Extensible for new lesson types without modifying existing code
- Extensible for new question types without modifying the quiz engine
- Thread-safe enrollment and progress updates
Out of Scope:
- Instructor dashboards and analytics
- Course ratings and reviews
- Payment processing and refunds
- Downloadable content and attachments
- Live sessions and real-time collaboration
Example Inputs and Outputs
Scenario 1: Student enrolls and completes a lesson
- Input: Student "Alice" enrolls in "Java Fundamentals" (no prerequisites). She opens Module 1, Lesson 1 (a text lesson) and marks it complete.
- Expected: Enrollment created. Lesson 1 marked complete. Module 1 progress = 1/4 (25%). Course progress = 1/12 (8%). Lesson 2 in Module 1 is now unlocked.
- Why: Validates enrollment, lesson completion, progress calculation, and sequential unlock.
Scenario 2: Student attempts a quiz lesson
- Input: Alice reaches Module 1, Lesson 3 (a quiz lesson with passing threshold 70%). She answers 2 out of 3 questions correctly (67%).
- Expected: Quiz scored at 67%. Lesson NOT marked complete (below threshold). Alice can retake. Progress unchanged.
- Why: Validates quiz grading, pass/fail logic, and retake behavior.
Scenario 3: Student earns a certificate
- Input: Alice completes all 12 lessons across 3 modules in "Java Fundamentals," including passing all quizzes.
- Expected: Course progress = 100%. Certificate generated with Alice's name, course title, completion date, and a unique certificate ID.
- Why: Validates full completion detection and certificate generation.
Try It Yourself
Try it yourself
Before reading the solution, spend 15-20 minutes sketching your own class diagram. Focus on the Course β Module β Lesson hierarchy and how you would track per-student progress separately from the course structure. The biggest design challenge is separating content (what the course contains) from progress (where each student stands).
Step 1: Identify Core Entities
Start by asking: what are the main "things" in this system? Look for nouns in your requirements. Each noun that has its own lifecycle or responsibilities becomes a candidate class.
A common mistake is merging Course and Enrollment into one class, or embedding progress data directly into Lesson objects. The course structure is shared across all students; progress is per-student. Mixing them means every student sees every other student's progress, or worse, you end up copying the entire course structure per enrollment.
| Entity | Responsibility | Key attributes |
|---|---|---|
| LearningPlatform | Orchestrator. Manages courses, users, enrollments. | courses, users, enrollments |
| User | Base class for students and instructors. Holds profile data. | id, name, email |
| Student | Enrolls in courses, takes lessons, earns certificates. | enrollments |
| Instructor | Creates and manages courses. | createdCourses |
| Course | Content container. Holds ordered modules and prerequisites. | title, modules, prerequisites |
| Module | Groups related lessons. Ordered within a course. | title, lessons, orderIndex |
| Lesson | Abstract base. Represents a single learning unit. | title, orderIndex, type |
| VideoLesson | Lesson with a video URL and duration. | videoUrl, durationMinutes |
| TextLesson | Lesson with text/HTML content. | content |
| QuizLesson | Lesson backed by a Quiz. Requires passing score. | quiz |
| Enrollment | Links a student to a course. Tracks per-lesson progress. | student, course, lessonProgress, certificate |
| Progress | Tracks completion status of a single lesson for a student. | lesson, completed, quizScore |
| Quiz | Collection of questions with a passing threshold. | questions, passingScore |
| Question | Abstract base for question types. | prompt, points |
| MCQQuestion | Multiple choice with one correct answer. | options, correctIndex |
| TrueFalseQuestion | Binary true/false question. | correctAnswer |
| FreeTextQuestion | Open-ended, graded by keyword matching. | keywords |
| Certificate | Proof of course completion. | id, studentName, courseTitle, issueDate |
Notice that Course, Module, and Lesson form the content hierarchy (shared, immutable during a student's session), while Enrollment and Progress form the student-specific tracking layer. Separating these two concerns is the single most important design decision in this problem.
Step 2: Define Relationships and Class Design
Now let's map out how these entities connect and derive their interfaces from the requirements.
Enrollment: The Central Orchestrator
The Enrollment class does the heavy lifting. It links a student to a course and owns all progression logic. Let's derive its interface from the requirements.
Deriving state from requirements:
| Requirement | What Enrollment must track |
|---|---|
| "Progress per lesson" | Map from lesson ID to Progress object |
| "Sequential unlock" | Access to the progress map to check previous lesson completion |
| "Certificate on full completion" | A nullable Certificate field |
| "Quiz retakes" | Latest quiz score stored in Progress |
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "Mark a lesson complete" | completeLesson(lessonId) |
| "Take a quiz" | attemptQuiz(lessonId, answers) |
| "Check if lesson is accessible" | isLessonAccessible(lessonId) |
| "Module progress %" | getModuleProgress(module) |
| "Course progress %" | getCourseProgress() |
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.