How to Design an Online Education & Learning Management System
A Comprehensive Senior-Level Guide — From Coursera-Scale Video Streaming to AI-Powered Adaptive Learning
Table of Contents
1. Introduction & Scope
The global e-learning market is projected to exceed $400 billion by 2026. Platforms like Coursera, Canvas LMS, Udemy, and edX serve millions of concurrent learners, thousands of institutions, and petabytes of video content. Designing a system that handles live lectures, on-demand video, auto-graded assessments, peer reviews, certificates, and AI-driven personalization — all while meeting FERPA/GDPR compliance — is one of the most challenging system design exercises.
This guide walks through every major subsystem of an enterprise-grade Learning Management System (LMS), from content ingestion pipelines to real-time virtual classrooms, covering architecture decisions, data models, API contracts, code samples, cost breakdowns, and interview-ready answers.
Why This Design Matters
Online education platforms face unique engineering challenges that combine the complexity of video streaming platforms (like YouTube), social networks (like Reddit for discussion forums), e-commerce systems (for payments), real-time collaboration tools (for live classrooms), and data-heavy analytics dashboards. Unlike a simple CRUD application, an LMS must handle massive content ingestion pipelines, real-time bidirectional media streams, complex grading workflows with rubrics and peer reviews, strict regulatory compliance, and personalized AI-driven experiences — all while maintaining sub-second response times and 99.95% availability.
The design we present here draws from real-world patterns used by Coursera (serving 130M+ learners), Canvas Instructure (used by thousands of institutions worldwide), edX (now part of 2U), and Udemy (serving 70M+ students). We cover the full vertical stack — from the CDN edge to the database layer, from WebRTC signaling servers to machine learning pipelines — providing you with a comprehensive blueprint that you can adapt to your own requirements.
Key Design Tradeoffs
Throughout this article, we will encounter several fundamental tradeoffs:
- Consistency vs. Availability: Grade submissions require strong consistency (a student's grade must never be lost or duplicated), while forum posts can tolerate eventual consistency for better availability.
- Latency vs. Cost: Serving video from edge CDN nodes reduces latency but increases cost. We balance this by tiering content popularity — hot content at the edge, cold content at the origin.
- Real-time vs. Batch: Progress tracking can be batch-processed for analytics but must update in real-time on the student dashboard. We use separate paths for each.
- Security vs. Usability: DRM and anti-cheat measures add friction. We implement them progressively — heavy for high-stakes exams, lighter for practice quizzes.
- Personalization vs. Privacy: AI recommendations improve engagement but require tracking user behavior. We anonymize analytics data and provide opt-out controls to comply with GDPR.
2. Functional & Non-Functional Requirements
Functional Requirements
| Capability | Description |
|---|---|
| Course Catalog | Browse, search, filter by topic/rating/instructor; full-text search with Elasticsearch |
| Course Content | Video lectures, readings, quizzes, assignments, Jupyter notebooks, SCORM packages |
| Video Streaming | HLS adaptive streaming with DRM (Widevine/FairPlay); subtitle support; playback speed control |
| Quizzes & Assessments | Multiple-choice, fill-in-blank, code execution, timed exams with anti-cheat proctoring |
| Assignments | File upload, code submission, rubric-based grading, peer review workflows |
| Discussion Forums | Threaded discussions per course/lecture; upvoting; instructor badges; markdown support |
| Progress Tracking | Per-module completion, time spent, quiz scores, overall grade calculation |
| Certificates | PDF generation with verification URL, blockchain-anchored credentials |
| Enrollment & Payment | Free/paid courses, subscription plans, institutional licenses, Stripe/PayPal integration |
| Live Classrooms | WebRTC-based live lectures with screen share, chat, breakout rooms, recording |
| Mobile Offline | Download videos for offline viewing; sync progress when online |
| AI Features | Adaptive learning paths, content recommendations, auto-generated summaries |
Non-Functional Requirements
| Attribute | Target |
|---|---|
| Availability | 99.95% (4.38 hrs downtime/year) |
| Latency | < 200ms for API calls; < 2s for video start |
| Throughput | 50K requests/sec; 500K concurrent video streams |
| Storage | 2 PB video + 500 GB metadata; 100% encrypted at rest |
| Scalability | Horizontal scaling to 100M users |
| Compliance | FERPA, GDPR, COPPA, SOC 2 Type II |
| DRM | Widevine L1, FairPlay Streaming, PlayReady |
3. High-Level Architecture
Architecture Principles
- Microservices: Each domain owns its data store; services communicate via async events (Kafka) and sync gRPC for low-latency queries.
- CQRS: Separate read and write models for the course catalog (write to PostgreSQL, read from Elasticsearch).
- Event Sourcing: Enrollment state changes are immutable events — enables audit trails for FERPA compliance.
- Domain-Driven Design: Bounded contexts for Course, Enrollment, Assessment, Payment, and Certificate.
Database Sharding Strategy
With 10M+ users, a single PostgreSQL instance cannot handle the write throughput. We shard the enrollments, submissions, and forum_posts tables by user_id using consistent hashing. The courses and modules tables remain unsharded (they fit comfortably on a single primary with read replicas) because they are read-heavy and have far fewer rows. We use Citus (a PostgreSQL extension) for transparent horizontal sharding. Connection routing is handled by PgBouncer with shard-aware connection pooling.
For the video_progress table, which receives high write volume (heartbeat every 10 seconds per active viewer), we use TimescaleDB hypertables partitioned by time. This gives us efficient range queries for "get today's watch time" and automatic data retention policies — raw progress data older than 90 days is rolled up into daily aggregates.
Service Communication Patterns
| Pattern | Usage | Technology |
|---|---|---|
| Async Event | Enrollment completed, grade posted, video uploaded | Apache Kafka (with Debezium CDC for DB changes) |
| Synchronous RPC | User lookup, course metadata, authorization checks | gRPC with protobuf serialization |
| API Gateway | External REST API for mobile/web clients | YARP (Yet Another Reverse Proxy) on .NET 8 |
| WebSocket | Live classroom signaling, real-time notifications, forum updates | SignalR (backed by Redis for scale-out) |
| Publish-Subscribe | Notification fanout, analytics event distribution | Kafka topics with consumer groups |
| Saga Pattern | Multi-step enrollment (payment → enrollment → notification) | Choreography-based saga via Kafka events |
Resilience Patterns
- Circuit Breaker: Polly-based circuit breakers on all inter-service calls. If the payment service is down, enrollment requests queue for retry rather than failing immediately.
- Bulkhead Isolation: Separate thread pools for critical (grading) vs. non-critical (analytics) operations within each service.
- Retry with Exponential Backoff: All Kafka producers and gRPC clients use jittered exponential backoff (100ms base, 30s max, 5 retries).
- Graceful Degradation: If the AI recommendation service is down, fall back to popularity-based recommendations. If Elasticsearch is down, fall back to PostgreSQL LIKE queries with caching.
4. Course Catalog & Content Management
Data Model
Content Management Pipeline
Instructors upload content through a rich editor (built on TipTap/ProseMirror). Videos are uploaded via resumable multipart upload to a presigned S3 URL, then processed by a transcoding pipeline:
Content Item Entity
public class ContentItem
{
public Guid Id { get; set; }
public Guid LessonId { get; set; }
public ContentType Type { get; set; } // Video, Document, Quiz, Assignment, SCORM
public string FileUrl { get; set; }
public string MimeType { get; set; }
public long SizeBytes { get; set; }
public ContentMetadata Metadata { get; set; }
public ContentStatus Status { get; set; } // Processing, Ready, Failed
public DateTime CreatedAt { get; set; }
}
public class ContentMetadata
{
public int? DurationSeconds { get; set; }
public string Language { get; set; }
public List Subtitles { get; set; } // ["en", "es", "fr"]
public Dictionary<string, string> TranscodeUrls { get; set; } // {"720p": "/hls/.../720p.m3u8"}
public string ThumbnailUrl { get; set; }
public string TranscriptUrl { get; set; }
} C#
Search Architecture
The catalog uses Elasticsearch with a custom analyzers for full-text search. Course data flows from PostgreSQL → Debezium CDC → Kafka → Elasticsearch index builder service. Faceted search supports:
- Topic/category hierarchy (multi-select)
- Difficulty level (Beginner, Intermediate, Advanced)
- Rating range (star filter)
- Duration (under 4h, 4-20h, 20h+)
- Language, subtitle availability
- Price range, free/paid
- Instructor, institution
5. Video Hosting & Streaming (HLS)
HLS Adaptive Streaming
HTTP Live Streaming (HLS) is the backbone of video delivery. Each video is transcoded into multiple bitrate renditions and segmented into 6-second TS chunks:
| Rendition | Resolution | Bitrate | Codec |
|---|---|---|---|
| Low | 640×360 | 800 Kbps | H.264 Baseline |
| Medium | 1280×720 | 2.5 Mbps | H.264 Main |
| High | 1920×1080 | 5 Mbps | H.264 High |
| 4K | 3840×2160 | 15 Mbps | H.265 Main10 |
(MOV/MP4)"] --> B["AWS MediaConvert
Transcoding"] B --> C["360p HLS
segments"] B --> D["720p HLS
segments"] B --> E["1080p HLS
segments"] B --> F["Audio-only
AAC"] B --> G["Thumbnails"] C --> H["S3 Bucket
(HLS Origin)"] D --> H E --> H F --> H G --> H H --> I["CloudFront CDN"] I --> J["HLS Player
(Video.js)"]
Master Playlist Example
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2"
stream_360p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720,CODECS="avc1.4d401f,mp4a.40.2"
stream_720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
stream_1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=15000000,RESOLUTION=3840x2160,CODECS="hev1.1.6.L150,mp4a.40.2"
stream_4k.m3u8HLS
Video Service Implementation
public class VideoService : IVideoService
{
private readonly IBlobStorage _storage;
private readonly ITranscodeOrchestrator _transcoder;
private readonly ICacheService _cache;
public async Task<VideoSession> CreatePlaybackSessionAsync(
Guid videoId, Guid userId, CancellationToken ct)
{
var video = await _cache.GetOrSetAsync(
$"video:{videoId}",
() => _repo.GetVideoAsync(videoId, ct),
TimeSpan.FromMinutes(30));
if (video == null)
throw new NotFoundException($"Video {videoId} not found");
var playbackUrl = await _storage.GetSignedUrlAsync(
video.HlsManifestPath,
TimeSpan.FromHours(1));
var progress = await _progressRepo
.GetLastPositionAsync(userId, videoId);
await _analytics.TrackAsync(new VideoPlayEvent
{
UserId = userId,
VideoId = videoId,
Timestamp = DateTime.UtcNow
});
return new VideoSession
{
VideoId = videoId,
ManifestUrl = playbackUrl,
Subtitles = video.Subtitles,
Duration = video.DurationSeconds,
ResumePositionSeconds = progress?.PositionSeconds ?? 0,
ThumbnailSpriteUrl = video.SpriteThumbnailUrl
};
}
}C#
Bandwidth Cost Analysis
| Metric | Value |
|---|---|
| Average video length | 12 minutes |
| Average views per video per month | 5,000 |
| Estimated total views/month | 250 million |
| Blended bandwidth cost (CloudFront) | $0.02/GB |
| Average session size (adaptive) | 900 MB |
| Monthly bandwidth cost | ~$4.5M/month |
| With CDN caching (60% hit rate) | ~$1.8M/month |
6. Interactive Quizzes & Assessments
Question Types
| Type | Grading | Anti-Cheat |
|---|---|---|
| Multiple Choice | Auto | Question bank randomization |
| Multiple Select | Auto (partial credit) | Option shuffling |
| Fill-in-the-Blank | Auto (fuzzy match) | Plaintext normalization |
| Code Execution | Auto (test suites) | Sandboxed execution (Docker) |
| Essay / Short Answer | Manual / AI-assisted | Plagiarism check |
| File Upload | Manual / Rubric | Originality scan |
Quiz Data Model
public class Quiz
{
public Guid Id { get; set; }
public Guid LessonId { get; set; }
public string Title { get; set; }
public int TimeLimitMinutes { get; set; }
public int MaxAttempts { get; set; }
public bool ShuffleQuestions { get; set; }
public bool ShowAnswersAfterSubmission { get; set; }
public decimal PassingScore { get; set; }
public List<QuizQuestion> Questions { get; set; }
}
public class QuizQuestion
{
public Guid Id { get; set; }
public QuestionType Type { get; set; }
public string Prompt { get; set; }
public string? CodeTemplate { get; set; }
public List<AnswerOption> Options { get; set; }
public string CorrectAnswer { get; set; }
public decimal Points { get; set; }
public string? Explanation { get; set; }
public string? TestCaseJson { get; set; }
}
public class QuizAttempt
{
public Guid Id { get; set; }
public Guid QuizId { get; set; }
public Guid StudentId { get; set; }
public DateTime StartedAt { get; set; }
public DateTime? SubmittedAt { get; set; }
public decimal? Score { get; set; }
public QuizAttemptStatus Status { get; set; }
public List<QuestionResponse> Responses { get; set; }
}C#
Code Execution Sandbox
For coding quizzes, submissions run in isolated Docker containers with resource limits. The execution service:
- Receives code submission via gRPC
- Creates an ephemeral Docker container with the required language runtime
- Sets CPU limit (0.5 vCPU), memory limit (256 MB), network disabled
- Runs test cases against the submission
- Streams stdout/stderr and test results back
- Kills container after timeout (30 seconds)
public class CodeExecutionService : ICodeExecutionService
{
private readonly IDockerClient _docker;
public async Task<ExecutionResult> RunAsync(
CodeSubmission submission, List<TestCase> testCases, CancellationToken ct)
{
var containerParams = new CreateContainerParameters
{
Image = GetImage(submission.Language),
Env = new[] { $"CODE={Convert.ToBase64String(Encoding.UTF8.GetBytes(submission.Code))}" },
HostConfig = new HostConfig
{
Memory = 256 * 1024 * 1024,
NanoCpus = 500_000_000,
NetworkDisabled = true,
AutoRemove = true
}
};
var container = await _docker.Containers.CreateContainerAsync(containerParams);
var started = await _docker.Containers.StartContainerAsync(
container.ID, new ContainerStartParameters());
var wait = await _docker.Containers.WaitContainerAsync(container.ID, ct);
var logs = await _docker.Containers.GetContainerLogsAsync(
container.ID,
new ContainerLogsParameters { Stdout = true, Stderr = true, Tail = "100" });
return EvaluateResults(logs, testCases, wait.StatusCode);
}
}C#
7. Assignment Submission & Grading (Rubrics)
Rubric-Based Grading
Rubrics define structured criteria for evaluating submissions. Each rubric has multiple dimensions, each with predefined levels and point values:
public class Rubric
{
public Guid Id { get; set; }
public Guid AssignmentId { get; set; }
public string Title { get; set; }
public List<RubricDimension> Dimensions { get; set; }
}
public class RubricDimension
{
public Guid Id { get; set; }
public string Name { get; set; } // e.g., "Code Quality", "Correctness", "Documentation"
public string Description { get; set; }
public int MaxPoints { get; set; }
public List<RubricLevel> Levels { get; set; }
}
public class RubricLevel
{
public Guid Id { get; set; }
public string Label { get; set; } // "Excellent", "Good", "Needs Improvement"
public string Description { get; set; }
public int Points { get; set; }
}
public class GradingResult
{
public Guid SubmissionId { get; set; }
public Guid GraderId { get; set; } // Instructor or Peer
public Dictionary<Guid, RubricScore> Scores { get; set; }
public string OverallFeedback { get; set; }
public decimal TotalScore { get; set; }
public decimal MaxPossible { get; set; }
public GradingStatus Status { get; set; }
}C#
Submission Flow
Peer Review Assignment Algorithm
For peer-reviewed assignments, we assign submissions to reviewers using a weighted matching algorithm that ensures:
- Each submission receives exactly 3 peer reviews
- No student reviews their own work
- Workload is balanced across reviewers
- Students in the same submission group don't review each other
- Historical review quality scores factor into reviewer selection
public class PeerReviewAssigner
{
public async Task<List<PeerAssignment>> AssignAsync(
Guid assignmentId, int reviewsPerSubmission, CancellationToken ct)
{
var submissions = await _repo.GetSubmissionsAsync(assignmentId, ct);
var reviewerPool = submissions.Select(s => s.StudentId).ToList();
var assignments = new List<PeerAssignment>();
var reviewCounts = reviewerPool.ToDictionary(id => id, _ => 0);
foreach (var submission in submissions)
{
var eligible = reviewerPool
.Where(id => id != submission.StudentId
&& !submission.GroupMembers.Contains(id))
.OrderBy(id => reviewCounts[id])
.ThenBy(_ => Guid.NewGuid()) // Random tiebreaker
.Take(reviewsPerSubmission)
.ToList();
foreach (var reviewerId in eligible)
{
assignments.Add(new PeerAssignment
{
SubmissionId = submission.Id,
ReviewerId = reviewerId,
DueAt = DateTime.UtcNow.AddDays(7)
});
reviewCounts[reviewerId]++;
}
}
return assignments;
}
}C#
8. Discussion Forums
Forum Architecture
Discussion forums are scoped to courses and optionally to individual lessons. Each forum uses a threaded model with support for markdown, code blocks, LaTeX, images, and instructor badges.
| Feature | Implementation |
|---|---|
| Threaded Replies | Adjacency list with materialized path for efficient subtree queries |
| Markdown Rendering | Server-side sanitize + client-side render (Marked.js) |
| Search | Elasticsearch index with course-scoped queries |
| Sorting | Popular (votes), Newest, Oldest, Instructor Answers First |
| Moderation | AI toxicity detection (Perspective API) + human review queue |
| Notifications | Thread followers receive notifications on new replies |
Post Entity
public class ForumPost
{
public Guid Id { get; set; }
public Guid ForumId { get; set; }
public Guid? ParentPostId { get; set; }
public Guid AuthorId { get; set; }
public string Content { get; set; } // Markdown
public string HtmlContent { get; set; } // Sanitized HTML
public string? ImageUrls { get; set; }
public int Upvotes { get; set; }
public int Downvotes { get; set; }
public bool IsInstructorAnswer { get; set; }
public bool IsAcceptedAnswer { get; set; }
public string MaterializedPath { get; set; } // "/uuid1/uuid2/"
public DateTime CreatedAt { get; set; }
public DateTime? LastEditedAt { get; set; }
public PostStatus Status { get; set; } // Active, Flagged, Hidden
}C#
Forum Engagement Features
Active discussion forums are one of the strongest predictors of course completion. To encourage engagement, we implement several social learning features:
- Answer Verification: Instructors can mark replies as "Accepted Answer" — these appear at the top and have a green checkmark, similar to Stack Overflow.
- Follow/Subscribe: Students can follow specific threads or entire forums. They receive notifications for new replies matching their subscription level.
- Mention System: Type
@instructoror@student-nameto tag specific users. Mentioned users receive a push notification with a deep link to the post. - Rich Embeds: Paste a YouTube link → inline player. Paste a GitHub gist → syntax-highlighted code block. Paste an image → inline thumbnail with lightbox.
- Stale Thread Detection: Threads with no activity for 30+ days are flagged as "Potentially Stale" — the system auto-suggests similar answered threads and optionally notifies the original poster.
- Reputation System: Helpful forum contributors earn reputation points based on upvotes and accepted answers. High-reputation users gain moderation privileges (flag posts, edit others' posts, close duplicate threads).
For international courses, we integrate auto-translation on forum posts using the NLLB-200 model, allowing students who speak different languages to interact within the same forum. Each post shows a "Translate" button, and instructors can configure default languages for their course forums.
9. Progress Tracking & Analytics
Event-Driven Progress Tracking
Every student interaction generates an event that flows through Kafka into the analytics pipeline:
(Time Series)"] DW["BigQuery
(Data Warehouse)"] REDIS["Redis
(Live Counts)"] end VP -->|"VideoProgressEvent"| K QZ -->|"QuizCompletedEvent"| K AS -->|"AssignmentSubmittedEvent"| K FR -->|"ForumActivityEvent"| K K --> FINK K --> SPARK FINK --> TS FINK --> REDIS SPARK --> DW
Progress Calculation Service
public class ProgressService : IProgressService
{
public async Task<CourseProgress> GetProgressAsync(
Guid userId, Guid courseId, CancellationToken ct)
{
var course = await _courseService.GetCourseAsync(courseId, ct);
var events = await _eventStore
.GetEventsAsync(userId, courseId, ct);
var moduleProgress = new List<ModuleProgress>();
foreach (var module in course.Modules)
{
var lessons = module.Lessons;
var completedLessons = lessons.Count(lesson =>
events.Any(e =>
e.EventType == "LessonCompleted"
&& e.EntityId == lesson.Id));
moduleProgress.Add(new ModuleProgress
{
ModuleId = module.Id,
ModuleTitle = module.Title,
TotalLessons = lessons.Count,
CompletedLessons = completedLessons,
Percentage = (decimal)completedLessons / lessons.Count * 100,
TimeSpentSeconds = events
.Where(e => e.EntityId == module.Id
&& e.EventType == "VideoProgress")
.Sum(e => e.Payload.GetProperty("duration").GetInt32())
});
}
var totalLessons = moduleProgress.Sum(m => m.TotalLessons);
var completedLessons = moduleProgress.Sum(m => m.CompletedLessons);
return new CourseProgress
{
CourseId = courseId,
UserId = userId,
OverallPercentage = totalLessons > 0
? (decimal)completedLessons / totalLessons * 100
: 0,
Modules = moduleProgress,
LastActivityAt = events.MaxOrDefault(e => e.Timestamp),
EstimatedCompletionDate = EstimateCompletion(
moduleProgress, events)
};
}
}C#
Analytics Dashboard Metrics
| Metric | Source | Refresh Rate |
|---|---|---|
| Course enrollment count | PostgreSQL | Real-time |
| Video completion rate | Flink aggregate | 5 minutes |
| Average quiz score | TimescaleDB | 15 minutes |
| Forum engagement (posts/week) | Elasticsearch | 1 hour |
| Student retention (D1/D7/D30) | BigQuery | Daily |
| Revenue per course | PostgreSQL | Daily |
| NPS score | Survey service | Weekly |
10. Certificate Generation
Certificate Pipeline
When a student completes all required modules and passes the final assessment, the certificate service:
- Validates completion criteria (all modules 100%, final exam passed)
- Generates a unique credential ID (UUID v4)
- Renders a PDF certificate using a template (Puppeteer or QuestPDF)
- Stores the PDF in S3
- Optionally anchors the credential hash on a blockchain (Hyperledger)
- Sends notification email with download link
public class CertificateService : ICertificateService
{
private readonly ICertificateRenderer _renderer;
private readonly IBlobStorage _storage;
private readonly IBlockchainAnchor _blockchain;
public async Task<Certificate> IssueAsync(
Guid userId, Guid courseId, CompletionRecord completion, CancellationToken ct)
{
var credentialId = Guid.NewGuid();
var student = await _userService.GetUserAsync(userId, ct);
var course = await _courseService.GetCourseAsync(courseId, ct);
var certificateData = new CertificateData
{
CredentialId = credentialId,
StudentName = student.FullName,
CourseName = course.Title,
InstructorName = course.Instructor.Name,
InstitutionName = course.Institution.Name,
CompletionDate = DateTime.UtcNow,
Grade = completion.FinalGrade,
VerificationUrl = $"https://lms.example.com/verify/{credentialId}"
};
var pdfBytes = await _renderer.RenderAsync("certificate-template", certificateData);
var s3Key = $"certificates/{credentialId}.pdf";
await _storage.UploadAsync(s3Key, pdfBytes, "application/pdf");
var hash = ComputeSha256(pdfBytes);
var blockchainTx = await _blockchain.AnchorAsync(hash, ct);
var cert = new Certificate
{
Id = credentialId,
UserId = userId,
CourseId = courseId,
PdfUrl = s3Key,
VerificationHash = hash,
BlockchainTxId = blockchainTx,
IssuedAt = DateTime.UtcNow
};
await _repo.SaveAsync(cert, ct);
return cert;
}
}C#
11. Instructor Dashboard
Dashboard Components
| Widget | Data Source | Refresh |
|---|---|---|
| Total Enrollments (with trend) | Enrollment Service | Real-time |
| Revenue & Earnings | Payment Service | Daily |
| Student Engagement Heatmap | Analytics (Flink) | Hourly |
| Quiz Score Distribution | Grade Service | Real-time |
| Forum Activity Feed | Discussion Service | Real-time |
| Submission Queue (pending grading) | Grade Service | Real-time |
| Student At-Risk Alerts | ML Pipeline | Daily |
| Review & Rating Summary | Review Service | Daily |
Content Authoring Interface
Instructors manage their courses through a drag-and-drop course builder with:
- Module/Lesson Editor: Reorder via drag-and-drop (SortableJS)
- Rich Text Editor: TipTap with custom extensions for embeds
- Quiz Builder: Visual question editor with live preview
- Rubric Editor: Spreadsheet-like rubric configuration
- Preview Mode: View course as a student would see it
- Bulk Operations: Import SCORM packages, CSV grade uploads
Student At-Risk Detection
The ML pipeline analyzes engagement signals to identify at-risk students:
| Feature | Weight | Description |
|---|---|---|
| Login frequency decline | 0.20 | >50% drop from baseline in last 7 days |
| Video completion rate drop | 0.25 | Watching <30% of recent lectures |
| Assignment submission delay | 0.20 | Late submissions on 2+ consecutive assignments |
| Quiz score decline | 0.15 | Scores below passing on recent quizzes |
| Forum participation | 0.10 | No posts or replies in last 14 days |
| Video pause/rewind ratio | 0.10 | High rewinding indicates confusion |
12. Student Dashboard
Student Dashboard Layout
(with progress bars)"] WISHLIST["Wishlist"] CERTS["My Certificates"] end subgraph CENTER["Center Panel"] TODAY["Today's Learning Goals"] RECENT["Recent Activity"] RECOMMEND["Recommended For You"] end subgraph RIGHT["Right Panel"] STREAK["Learning Streak 🔥"] STATS["Stats: Hours, Courses, Rank"] CALENDAR["Upcoming Deadlines"] end end
Key Components
- Continue Learning: One-click resume to the last played video position (stored per-lesson in Redis)
- Learning Goals: Students set daily/weekly goals (e.g., "30 minutes/day"). Streak tracking motivates consistent learning.
- Deadline Calendar: Aggregates upcoming quiz deadlines, assignment due dates, and live session schedules
- Achievements Badges: Gamification — badges for completing milestones ("First Course", "100-Hour Learner", "Quiz Master")
- Notebook: Students can take timestamped notes linked to specific video moments
13. Enrollment & Payment Processing
Enrollment Flow
Enrollment States
| State | Trigger | Description |
|---|---|---|
| Pending | Click "Enroll" | Awaiting payment confirmation |
| Active | Payment confirmed | Full access to course content |
| Audit | Select "Audit" | Access to free content only, no certificate |
| Cohort | Cohort enrollment | Time-bound access with cohort schedule |
| Expired | Subscription ended | Access revoked, data retained 90 days |
| Refunded | Refund processed | Full refund, access revoked, audit trail |
Payment Service Implementation
public class PaymentService : IPaymentService
{
private readonly IStripeClient _stripe;
private readonly IPaymentRepository _repo;
private readonly IEventBus _events;
public async Task<PaymentIntentResult> CreatePaymentAsync(
PaymentRequest request, CancellationToken ct)
{
var options = new PaymentIntentCreateOptions
{
Amount = Convert.ToInt64(request.Amount * 100),
Currency = request.Currency.ToLower(),
Metadata = new Dictionary<string, string>
{
["course_id"] = request.CourseId.ToString(),
["user_id"] = request.UserId.ToString(),
["enrollment_id"] = request.EnrollmentId.ToString()
},
AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
{
Enabled = true
}
};
var intent = await _stripe.PaymentIntents.CreateAsync(options);
await _repo.SaveTransactionAsync(new PaymentTransaction
{
Id = Guid.NewGuid(),
StripePaymentIntentId = intent.Id,
UserId = request.UserId,
CourseId = request.CourseId,
Amount = request.Amount,
Currency = request.Currency,
Status = PaymentStatus.Pending,
CreatedAt = DateTime.UtcNow
}, ct);
return new PaymentIntentResult
{
ClientSecret = intent.ClientSecret,
PaymentIntentId = intent.Id
};
}
public async Task HandleWebhookAsync(string payload, string signature, CancellationToken ct)
{
var stripeEvent = _stripe.Webhook.ConstructEvent(
payload, signature, _config.WebhookSecret);
if (stripeEvent.Type == Events.PaymentIntentSucceeded)
{
var intent = stripeEvent.Data.Object as PaymentIntent;
var transaction = await _repo
.GetByStripeIdAsync(intent.Id, ct);
transaction.Status = PaymentStatus.Completed;
transaction.CompletedAt = DateTime.UtcNow;
await _repo.UpdateAsync(transaction, ct);
await _events.PublishAsync(new EnrollmentActivatedEvent
{
EnrollmentId = Guid.Parse(intent.Metadata["enrollment_id"]),
UserId = Guid.Parse(intent.Metadata["user_id"]),
CourseId = Guid.Parse(intent.Metadata["course_id"])
});
}
}
}C#
Subscription Plans
| Plan | Price | Access | Features |
|---|---|---|---|
| Free | $0 | Free courses only | Basic quizzes, forum access |
| Individual | $49/mo | All courses | Certificates, offline download |
| Team (5+) | $39/user/mo | All courses | Admin dashboard, SSO, analytics |
| Enterprise | Custom | All courses + custom | LTI integration, SCORM, SAML, SLA |
| Institutional | Custom | All courses + campus | Campus-wide license, LMS integration |
14. Cohort-Based Courses
Cohort Model
Cohort-based courses (like Cohort-based Massive Open Online Courses — cMOOCs) run on a fixed schedule with synchronized deadlines, live sessions, and peer interactions. This model dramatically increases completion rates from ~5% (self-paced) to ~60%.
public class Cohort
{
public Guid Id { get; set; }
public Guid CourseId { get; set; }
public string Name { get; set; } // "Fall 2026 Cohort"
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int MaxCapacity { get; set; }
public CohortStatus Status { get; set; }
public List<CohortSchedule> Schedule { get; set; }
public CohortPricing Pricing { get; set; }
}
public class CohortSchedule
{
public Guid Id { get; set; }
public Guid CohortId { get; set; }
public int WeekNumber { get; set; }
public string ModuleTitle { get; set; }
public DateTime LectureDate { get; set; }
public DateTime AssignmentDueDate { get; set; }
public DateTime QuizDueDate { get; set; }
public DateTime PeerReviewDeadline { get; set; }
public List<LiveSessionSlot> LiveSlots { get; set; }
}
public class CohortEnrollment
{
public Guid Id { get; set; }
public Guid CohortId { get; set; }
public Guid UserId { get; set; }
public DateTime EnrolledAt { get; set; }
public decimal ProgressPercent { get; set; }
public string TeamName { get; set; } // For group projects
public List<Guid> TeamMembers { get; set; }
}C#
Cohort Lifecycle
Opens"] --> WAIT["Waitlist
Phase"] WAIT --> ACT["Active
Cohort"] ACT --> WRAP["Wrap-Up
& Certificates"] WRAP --> ARCH["Archived
(Self-Paced)"] ACT --> WK1["Week 1:
Module 1"] ACT --> WK2["Week 2:
Module 2"] ACT --> WK3["Week N:
Module N"]
15. Live Virtual Classrooms (WebRTC)
Architecture
(MediaSoup)"] SFU2["SFU Node 2
(MediaSoup)"] end subgraph SUPPORT["Support Services"] CHAT["WebSocket Chat"] REC["Recording Service"] WHITEBOARD["Whiteboard Sync"] POLL["Polling Service"] end HOST -->|"publish: 1 video + 1 audio"| SFU1 STU1 -->|"publish: audio only"| SFU1 STU2 -->|"publish: audio only"| SFU1 SFU1 -->|"relay stream"| SFU2 SFU1 -->|"forward host video"| STU1 SFU1 -->|"forward host video"| STU2 HOST <--> CHAT SFU1 --> REC HOST <--> WHITEBOARD HOST <--> POLL
WebRTC Connection Flow
| Step | Action | Protocol |
|---|---|---|
| 1 | Join room → get session token | REST API |
| 2 | STUN/TURN credential exchange | HTTP |
| 3 | SDP offer/answer negotiation | WebSocket |
| 4 | ICE candidate exchange | WebSocket |
| 5 | Media stream publishing | WebRTC (DTLS/SRTP) |
| 6 | SFU forwards streams to subscribers | WebRTC |
| 7 | Simulcast: 3 layers (360p, 720p, 1080p) | WebRTC |
| 8 | Scalability: cascading SFUs | Internal SFU mesh |
Room Capacity
public class LiveClassroomService : ILiveClassroomService
{
private readonly IMediaSoupServer _mediaSoup;
private readonly IRecordingService _recorder;
public async Task<JoinResult> JoinRoomAsync(
Guid roomId, Guid userId, string role, CancellationToken ct)
{
var room = await _repo.GetRoomAsync(roomId, ct);
var sfuNode = await _loadBalancer.GetOptimalSfuAsync(room.Region);
var token = await _authService.GenerateRoomTokenAsync(
roomId, userId, role, TimeSpan.FromHours(4));
var peer = new Peer
{
UserId = userId,
Role = role,
SfuNodeId = sfuNode.Id,
JoinedAt = DateTime.UtcNow
};
await _repo.AddPeerAsync(roomId, peer, ct);
if (role == "instructor" && room.RecordingEnabled)
{
await _recorder.StartRecordingAsync(roomId, sfuNode.Id, ct);
}
return new JoinResult
{
Token = token,
SfuUrl = sfuNode.Url,
IceServers = await _getIceServers(),
RoomConfig = new RoomConfig
{
MaxBitrate = 2_500_000,
Simulcast = true,
ScalabilityMode = "L1T3"
}
};
}
}C#
16. Content DRM & Security
DRM Pipeline
(Upload)"] --> B["Transcode
(MediaConvert)"] B --> C["Encrypt
(Widevine/FairPlay)"] C --> D["License Server
Integration"] D --> E["Encrypted HLS
on S3"] E --> F["CloudFront CDN"] F --> G["Player decrypts
via CDM"]
Widevine + FairPlay Integration
| DRM System | Platform | License Server |
|---|---|---|
| Widevine (CENC) | Android, Chrome, Edge | BuyDRM / EZDRM |
| FairPlay Streaming | Safari, iOS, Apple TV | Apple FPS |
| PlayReady | Windows, Edge, Xbox | Microsoft PlayReady |
Content Protection Measures
- DRM Encryption: AES-128-CENC for Widevine, SAMPLE-AES for FairPlay
- Token Authentication: Signed HLS URLs with 1-hour expiry (CloudFront signed cookies)
- Domain Locking: Player only loads on approved domains
- Watermarking: Invisible forensic watermark on video frames (Castlabs, Irdeto)
- Screen Capture Detection: JavaScript detects screen-sharing APIs and displays overlay watermark with user ID
- Right-Click Disabled: On video player element (defense-in-depth, not sole protection)
- Secure Key Storage: DRM keys managed via AWS KMS, never exposed to client JavaScript
17. SCORM & xAPI Compliance
SCORM Support
SCORM (Sharable Content Object Reference Model) enables interoperability with legacy LMS content. We support SCORM 1.2 and SCORM 2004 4th Edition:
- Package Import: Upload .zip containing imsmanifest.xml → extract, parse, and index content objects
- Runtime API: iframe-based SCORM API bridge (API, API_1484_11) for CMI data exchange
- Data Model: Full support for cmi.core.lesson_status, cmi.suspend_data, cmi.score.*, cmi.interactions.*
- Sequencing: SCORM 2004 sequencing and navigation (flow/chunky)
xAPI (Experience API / Tin Can)
xAPI provides richer learning analytics than SCORM. We implement an LRS (Learning Record Store) that ingests xAPI statements:
// xAPI Statement Structure
{
"actor": {
"mbox": "mailto:student@example.com",
"name": "Jane Doe",
"objectType": "Agent"
},
"verb": {
"id": "http://adlnet.gov/expapi/verbs/completed",
"display": { "en-US": "completed" }
},
"object": {
"id": "https://lms.example.com/courses/system-design/lessons/video-1",
"definition": {
"name": { "en-US": "Introduction to System Design" },
"type": "http://adlnet.gov/expapi/activities/lesson"
}
},
"result": {
"score": { "scaled": 0.95, "raw": 95, "max": 100 },
"success": true,
"completion": true,
"duration": "PT12M30S"
},
"context": {
"contextActivities": {
"parent": [{ "id": "https://lms.example.com/courses/system-design" }]
}
},
"timestamp": "2026-07-12T10:30:00Z"
}JSON
Integration Matrix
| Standard | Use Case | Our Support |
|---|---|---|
| SCORM 1.2 | Legacy content packages | Full (import, runtime, scoring) |
| SCORM 2004 | Advanced sequencing | Full (including navigation) |
| xAPI | Rich learning analytics | Full (LRS + statement forwarding) |
| CMI5 | xAPI + SCORM hybrid | Full (launch flow + scoring) |
| LTI 1.3 | Tool interoperability | Full (Deep Linking, Grade Passback) |
18. Adaptive Learning Paths
Knowledge Graph Model
We build a knowledge graph in Neo4j where concepts are nodes and prerequisite relationships are edges. The adaptive engine uses this graph to create personalized learning paths:
// Neo4j Knowledge Graph Schema
CREATE (basics:Concept {
name: "Basic Programming",
difficulty: 1,
estimated_hours: 40
})
CREATE (ds:Concept {
name: "Data Structures",
difficulty: 2,
estimated_hours: 30
})
CREATE (sysdesign:Concept {
name: "System Design",
difficulty: 3,
estimated_hours: 60
})
CREATE (basics)-[:PREREQUISITE_OF {strength: 0.9}]->(ds)
CREATE (ds)-[:PREREQUISITE_OF {strength: 0.8}]->(sysdesign)
// Find personalized path for a student
MATCH path = (start:Concept)
WHERE NOT ()-[:PREREQUISITE_OF]->(start)
AND NOT (start)<-[:MASTERED_BY]-(student {id: $studentId})
MATCH path = (start)-[:PREREQUISITE_OF*0..6]->(target:Concept)
WHERE NOT (target)<-[:MASTERED_BY]-(student)
RETURN path ORDER BY reduce(s = 0, r IN relationships(path) | s + r.strength)
LIMIT 5Cypher
Adaptive Algorithm
public class AdaptiveLearningEngine : IAdaptiveLearningEngine
{
private readonly INeo4jClient _graph;
private readonly IPerformanceAnalyzer _analyzer;
public async Task<LearningPath> GeneratePathAsync(
Guid studentId, Guid goalConceptId, CancellationToken ct)
{
var masteredConcepts = await _graph.Cypher
.Match("(s:Student {id: $studentId})-[:MASTERED_BY]->(c:Concept)")
.WithParam("studentId", studentId)
.Return((c) => c.As<ConceptDto>())
.ResultsAsync;
var allPrereqs = await GetPrerequisiteChainAsync(goalConceptId, ct);
var gapAnalysis = allPrereqs
.Where(p => !masteredConcepts.Any(m => m.Id == p.Id))
.OrderBy(p => p.Difficulty)
.ToList();
var path = new LearningPath { Goal = goalConceptId };
foreach (var gap in gapAnalysis)
{
var courses = await _courseService
.FindCoursesForConceptAsync(gap.Id, ct);
var bestCourse = courses
.OrderByDescending(c => c.Rating)
.ThenBy(c => c.DurationHours)
.First();
path.Steps.Add(new LearningPathStep
{
ConceptId = gap.Id,
CourseId = bestCourse.Id,
EstimatedHours = bestCourse.DurationHours,
Difficulty = gap.Difficulty,
IsRecommended = true
});
}
path.TotalEstimatedHours = path.Steps.Sum(s => s.EstimatedHours);
path.DifficultyProgression = CalculateProgression(path.Steps);
return path;
}
}C#
19. AI-Powered Recommendations
Recommendation Engine Architecture
(viewing patterns)"] CBF["Content-Based Filtering
(course embeddings)"] KG["Knowledge Graph
(concept similarity)"] CONTEXT["Context Features
(time, device, location)"] end subgraph MODEL["ML Models"] CAND["Candidate Generation
(Two-Tower DNN)"] RANK["Ranking Model
(LambdaMART)"] RECENCY["Recency Boost"] end subgraph OUTPUT["Output"] HOME["Homepage Recommendations"] COURSE["Course Page: Similar Courses"] EMAIL["Email Digests"] PUSH["Push Notifications"] end UCF --> CAND CBF --> CAND KG --> CAND CONTEXT --> RANK CAND --> RANK RANK --> RECENCY RECENCY --> HOME RECENCY --> COURSE RECENCY --> EMAIL RECENCY --> PUSH
Two-Tower Model
// Two-Tower Architecture (TensorFlow)
// User Tower: encodes user history into embedding
// Item Tower: encodes course features into embedding
public class RecommendationModel
{
private readonly IEmbeddingService _embeddings;
private readonly ICandidateRetriever _candidates;
public async Task<List<RecommendedCourse>> GetRecommendationsAsync(
Guid userId, int topN, CancellationToken ct)
{
var userProfile = await _embeddings.GetUserEmbeddingAsync(userId, ct);
var recentActivity = await _activityStore
.GetRecentAsync(userId, days: 30);
var candidateCourses = await _candidates.RetrieveAsync(
userProfile,
excludeIds: recentActivity.Select(a => a.CourseId),
count: 200);
var scored = new List<ScoredCourse>();
foreach (var course in candidateCourses)
{
var courseEmbedding = await _embeddings
.GetCourseEmbeddingAsync(course.Id);
var similarity = CosineSimilarity(
userProfile.Vector, courseEmbedding.Vector);
var engagementScore = await _analyzer
.GetExpectedEngagementAsync(userId, course.Id);
var freshnessBoost = CalculateFreshness(course.PublishedAt);
scored.Add(new ScoredCourse
{
Course = course,
Score = (0.4 * similarity)
+ (0.35 * engagementScore)
+ (0.15 * course.Rating / 5.0)
+ (0.10 * freshnessBoost)
});
}
return scored
.OrderByDescending(s => s.Score)
.Take(topN)
.Select(s => new RecommendedCourse
{
Course = s.Course,
Confidence = s.Score,
Reason = GetReason(s)
})
.ToList();
}
private string GetReason(ScoredCourse s) => s switch
{
{ Score > 0.8 } => "Because you completed System Design",
{ Score > 0.6 } => "Popular in your area",
_ => "Trending this week"
};
}C#
Additional AI Features
| Feature | Model | Input |
|---|---|---|
| Course Summary | GPT-4 Fine-tuned | Course syllabus + transcript |
| Auto-Generated Quiz Questions | GPT-4 + Verification | Lecture transcript + learning objectives |
| Smart Search (Semantic) | Sentence-BERT | User query → embedding → vector search |
| Dropout Prediction | XGBoost | Engagement features (see Section 11) |
| Content Quality Scoring | Ensemble | Engagement metrics + reviews + completion |
| Translation | NLLB-200 | Subtitles → 100+ languages |
20. Peer Review Workflows
Review Pipeline
Outlier Detection
When aggregating peer review scores, we detect and handle outliers:
- If all 3 reviewers agree (σ < 0.2), average all scores
- If 1 reviewer is a statistical outlier (Z-score > 2), weight them at 20% instead of 33%
- If reviews are wildly divergent (range > 60 points on 100-point scale), flag for instructor review
- Reviewers who consistently produce outlier reviews get their future reviews down-weighted
public class ReviewAggregator
{
public AggregatedScore Aggregate(List<PeerReview> reviews)
{
var scores = reviews.Select(r => r.TotalScore).ToList();
var mean = scores.Average();
var stdDev = Math.Sqrt(scores.Average(s => Math.Pow(s - mean, 2)));
if (stdDev < 20) // Tight agreement
{
return new AggregatedScore
{
FinalScore = mean,
Confidence = 0.95,
Method = AggregationMethod.SimpleAverage
};
}
// Identify outliers using Modified Z-Score
var median = GetMedian(scores);
var mad = scores.Average(s => Math.Abs(s - median)) * 1.4826;
var weights = reviews.Select(r =>
{
var zScore = Math.Abs(r.TotalScore - median) / mad;
return zScore > 2 ? 0.2 : 1.0;
}).ToList();
var totalWeight = weights.Sum();
var weightedScore = reviews
.Zip(weights, (r, w) => r.TotalScore * w)
.Sum() / totalWeight;
return new AggregatedScore
{
FinalScore = weightedScore,
Confidence = 0.75,
Method = AggregationMethod.WeightedOutlierExcluded,
FlaggedForInstructorReview = stdDev > 40
};
}
}C#
21. Plagiarism Detection
Multi-Layer Detection
| Layer | Technique | Catches |
|---|---|---|
| Text Similarity | TF-IDF + MinHash + SimHash | Copy-paste, minor paraphrasing |
| Semantic Similarity | Sentence-BERT cosine similarity | Deep paraphrasing, idea theft |
| Code Plagiarism | AST-based comparison (Moss-like) | Variable renaming, reordering |
| Code Structure | Control flow graph similarity | Algorithmic plagiarism |
| Source Search | Google Custom Search API | Internet-sourced content |
| Intra-Course | Submission-to-submission comparison | Peer copying within cohort |
Plagiarism Check Pipeline
public class PlagiarismDetector : IPlagiarismDetector
{
private readonly ITextSimilarityEngine _textEngine;
private readonly ICodeSimilarityEngine _codeEngine;
private readonly ISourceSearchEngine _searchEngine;
public async Task<PlagiarismReport> CheckAsync(
Submission submission, CancellationToken ct)
{
var report = new PlagiarismReport { SubmissionId = submission.Id };
if (submission.Type == SubmissionType.Code)
{
var codeMatches = await _codeEngine
.CompareCodeAsync(
submission.CodeContent,
submission.Language,
submission.CourseId);
report.CodeMatches = codeMatches.Select(m => new PlagiarismMatch
{
SourceId = m.SourceSubmissionId,
Similarity = m.AstSimilarity,
MatchedSegments = m.MatchedNodes,
Confidence = m.Confidence
}).ToList();
}
var textMatches = await _textEngine
.CompareTextAsync(submission.TextContent, submission.CourseId);
report.TextMatches = textMatches;
var webMatches = await _searchEngine
.SearchSourcesAsync(submission.TextContent);
report.WebSources = webMatches;
report.OverallScore = CalculateCompositeScore(report);
report.Status = report.OverallScore > 70
? PlagiarismStatus.HighRisk
: report.OverallScore > 40
? PlagiarismStatus.MediumRisk
: PlagiarismStatus.LowRisk;
return report;
}
}C#
22. Mobile & Offline Access
Offline Sync Architecture
Offline Content Strategy
| Content Type | Offline Support | Storage |
|---|---|---|
| Video lectures | Downloaded via "Save for Offline" | 360p/720p HLS segments in app sandbox |
| Course readings | Auto-synced on enrollment | SQLite (rendered HTML) |
| Quizzes | Cached questions, submit when online | SQLite |
| Forum posts | Read-only cached; compose offline, send on reconnect | SQLite |
| Progress data | Stored locally, synced on reconnect | SQLite + conflict resolution |
Conflict Resolution
public class OfflineSyncResolver
{
public SyncResolution Resolve(
LocalRecord local, RemoteRecord remote)
{
// Last-Write-Wins with server timestamp as authority
if (remote.UpdatedAt > local.UpdatedAt)
{
return new SyncResolution
{
Winner = SyncSource.Remote,
MergedData = remote.Data,
ConflictDetected = local.UpdatedAt != remote.UpdatedAt
};
}
// For progress data: use MAX value (never lose progress)
if (local.DataType == DataType.Progress)
{
var merged = MergeProgressData(local.Data, remote.Data);
return new SyncResolution
{
Winner = SyncSource.Merged,
MergedData = merged,
ConflictDetected = false
};
}
return new SyncResolution
{
Winner = SyncSource.Local,
MergedData = local.Data
};
}
}C#
Mobile Performance Optimization
The mobile app uses several optimization techniques to ensure smooth performance on lower-end devices:
- Lazy Loading: Course content is loaded progressively — module list loads first, lesson details load on expand. This reduces initial payload from 200KB to under 30KB.
- Image Optimization: Thumbnails use WebP format with automatic fallback to JPEG for older devices. Instructor avatars are cached locally with a 7-day TTL.
- Video Pre-fetching: When a student starts watching a lesson, the next 2 segments (~12 seconds) are pre-fetched in the background to ensure seamless playback.
- Background Sync: A background service runs every 15 minutes to sync local progress data with the server. This uses WorkManager on Android and Background Tasks on iOS, respecting battery optimization policies.
- Deep Linking: Push notifications deep-link directly to the relevant lesson, quiz, or discussion thread within the app, skipping multiple navigation steps.
23. Notifications System
Notification Channels
| Channel | Use Case | Delivery SLA |
|---|---|---|
| In-App | Activity feed, badge updates | Real-time (WebSocket) |
| Push (FCM/APNs) | Deadline reminders, new content | < 30 seconds |
| Email (SES) | Welcome, certificates, weekly digest | < 5 minutes |
| SMS (Twilio) | Live session starting, OTP | < 1 minute |
| Webhook | LMS integrations (LTI) | < 10 seconds |
Notification Preferences
public class NotificationPreferences
{
public Guid UserId { get; set; }
// Channel toggles
public bool EmailEnabled { get; set; } = true;
public bool PushEnabled { get; set; } = true;
public bool SmsEnabled { get; set; }
// Category preferences
public NotificationLevel CourseUpdates { get; set; } = NotificationLevel.All;
public NotificationLevel Deadlines { get; set; } = NotificationLevel.All;
public NotificationLevel ForumReplies { get; set; } = NotificationLevel.Mentions;
public NotificationLevel GradesAvailable { get; set; } = NotificationLevel.All;
public NotificationLevel LiveSessions { get; set; } = NotificationLevel.All;
public NotificationLevel Marketing { get; set; } = NotificationLevel.Off;
public NotificationLevel WeeklyDigest { get; set; } = NotificationLevel.Summary;
// Quiet hours
public TimeSpan? QuietHoursStart { get; set; } // e.g., 22:00
public TimeSpan? QuietHoursEnd { get; set; } // e.g., 07:00
public string Timezone { get; set; }
}
public enum NotificationLevel
{
All, // Every notification
Summary, // Daily/weekly digest
Mentions, // Only direct mentions
Off // No notifications
}C#
Event-Driven Architecture
24. Monitoring, Security & Compliance
Monitoring Stack
| Layer | Tool | Purpose |
|---|---|---|
| Metrics | Prometheus + Thanos | Time-series metrics, cross-region federation |
| Dashboards | Grafana | Real-time operational dashboards |
| Logging | ELK Stack (Elasticsearch, Logstash, Kibana) | Centralized structured logging |
| Tracing | Jaeger + OpenTelemetry | Distributed tracing for request flows |
| Alerting | PagerDuty + OpsGenie | Incident management and escalation |
| Error Tracking | Sentry | Client + server error aggregation |
| Uptime | Checkly / Pingdom | Synthetic monitoring, SLA tracking |
| Security | Snyk, OWASP ZAP | Dependency scanning, DAST |
Key SLIs / SLOs
| SLI | SLO Target | Error Budget (30d) |
|---|---|---|
| API Availability | 99.95% | 21.6 minutes |
| Video Start Time (p95) | < 2 seconds | 5% of sessions |
| API Latency (p99) | < 500ms | 1% of requests |
| Search Latency (p95) | < 200ms | 5% of queries |
| Data Durability | 99.999999% | 0 data loss events |
Security Architecture
- Authentication: OAuth 2.0 / OIDC via Auth0 with MFA support; SAML 2.0 for institutional SSO
- Authorization: RBAC (Student, TA, Instructor, Admin, SuperAdmin) + ABAC for course-level permissions
- Data Encryption: AES-256-GCM at rest (AWS KMS), TLS 1.3 in transit
- Secret Management: HashiCorp Vault with dynamic database credentials
- API Security: Rate limiting (100 req/min/user), input validation (FluentValidation), OWASP Top 10 mitigation
- CSRF/XSS: SameSite cookies, Content-Security-Policy headers, server-side HTML sanitization (HtmlSanitizer NuGet)
Compliance Requirements
| Regulation | Scope | Key Requirements |
|---|---|---|
| FERPA | Student education records (US) | No unauthorized disclosure, right to inspect, annual notice |
| GDPR | EU residents' personal data | Consent, data minimization, right to erasure, DPO appointment |
| COPPA | Children under 13 (US) | Parental consent, limited data collection |
| SOC 2 Type II | Enterprise customers | Annual audit of security controls, availability, confidentiality |
| HIPAA | Health-related courses | BAA with cloud providers, encryption, access logging |
Incident Response Playbook
We maintain a documented incident response process with clear severity levels and escalation paths:
| Severity | Example | Response Time | Escalation |
|---|---|---|---|
| SEV-1 | Video streaming down, payment processing failing | 15 minutes | VP Engineering, all-hands Slack |
| SEV-2 | Quiz submissions failing for one course | 1 hour | On-call SRE, team lead |
| SEV-3 | Search returning stale results | 4 hours | On-call engineer |
| SEV-4 | Minor UI bug on certificate page | Next sprint | Jira ticket |
Post-incident, we conduct blameless retrospectives within 48 hours, generate a written post-mortem, and track action items to completion. We maintain a public status page (built on Cachet) where students and instructors can check system health in real-time. All SEV-1 and SEV-2 incidents require a root cause analysis document that identifies the 5 Whys and proposes both immediate fixes and long-term architectural improvements to prevent recurrence.
25. Cost Estimation & API Design
Monthly Infrastructure Cost (10M users)
| Service | Spec | Monthly Cost |
|---|---|---|
| EKS Kubernetes | 50 m5.2xlarge nodes | $43,000 |
| RDS PostgreSQL | db.r6g.2xlarge Multi-AZ × 3 | $12,000 |
| ElastiCache Redis | r6g.xlarge cluster × 6 | $6,500 |
| Elasticsearch | m5.2xlarge × 6 nodes | $8,400 |
| Apache Kafka (MSK) | kafka.m5.2xlarge × 6 | $5,700 |
| S3 Storage | 2 PB video + 500 GB metadata | $46,000 |
| CloudFront CDN | 150 TB/month transfer | $13,000 |
| MediaConvert | 10,000 hours transcoding/month | $3,000 |
| Neo4j AuraDB | Professional (knowledge graph) | $2,000 |
| ML Infrastructure | 2 × g5.xlarge (inference) | $4,500 |
| MongoDB (xAPI LRS) | M40 cluster | $1,800 |
| AWS WAF + Shield | DDoS protection | $2,500 |
| Observability (Datadog) | Full-stack monitoring | $8,000 |
| Email (SES) | 5M emails/month | $1,000 |
| Total Infrastructure | $~157,400/month |
Team Cost Estimate
| Role | Headcount | Annual Cost (Loaded) |
|---|---|---|
| Backend Engineers | 12 | $1,800,000 |
| Frontend Engineers | 8 | $1,120,000 |
| Mobile Engineers | 6 | $900,000 |
| ML Engineers | 4 | $720,000 |
| DevOps/SRE | 4 | $600,000 |
| QA Engineers | 4 | $480,000 |
| Engineering Managers | 3 | $540,000 |
| Product Manager | 2 | $300,000 |
| Designer | 2 | $240,000 |
| Total Team | 45 | $6,700,000/year |
API Design Principles
All APIs follow RESTful conventions with consistent JSON response envelopes. Every response includes a meta field with request ID, timestamp, and pagination info. Errors use RFC 7807 Problem Details format. We version our APIs via URL path (/api/v1/) and deprecate old versions with a 6-month sunset window. Rate limits are enforced per-user with Redis sliding window counters, returning 429 Too Many Requests with Retry-After headers.
// Standard Response Envelope
{
"data": { ... },
"meta": {
"requestId": "req_a1b2c3d4",
"timestamp": "2026-07-12T10:30:00Z",
"pagination": {
"page": 1,
"pageSize": 20,
"totalItems": 156,
"totalPages": 8
}
}
}
// Standard Error Response (RFC 7807)
{
"type": "https://api.lms.example.com/errors/not-found",
"title": "Course Not Found",
"status": 404,
"detail": "Course with ID 'abc-123' does not exist or has been unpublished.",
"instance": "/api/v1/courses/abc-123"
}JSON
Core API Endpoints
// Course APIs
GET /api/v1/courses # List courses (with filters, pagination)
GET /api/v1/courses/{id} # Course detail
POST /api/v1/courses # Create course (instructor)
PUT /api/v1/courses/{id} # Update course
GET /api/v1/courses/{id}/modules # List modules
POST /api/v1/courses/{id}/modules # Add module
// Enrollment APIs
POST /api/v1/enrollments # Enroll in course
GET /api/v1/enrollments # My enrollments
GET /api/v1/enrollments/{id}/progress # Course progress
DELETE /api/v1/enrollments/{id} # Unenroll
// Video APIs
GET /api/v1/videos/{id}/session # Get playback session (signed URL)
POST /api/v1/videos/{id}/progress # Update watch position
GET /api/v1/videos/{id}/subtitles # Get subtitles
// Quiz APIs
GET /api/v1/quizzes/{id} # Get quiz (with randomized questions)
POST /api/v1/quizzes/{id}/attempts # Start attempt
POST /api/v1/quizzes/{id}/submit # Submit attempt
// Assignment APIs
POST /api/v1/assignments/{id}/submit # Submit assignment
GET /api/v1/assignments/{id}/rubric # Get rubric
POST /api/v1/assignments/{id}/review # Submit peer review
// Forum APIs
GET /api/v1/forums/{id}/posts # List posts
POST /api/v1/forums/{id}/posts # Create post
POST /api/v1/posts/{id}/reply # Reply to post
POST /api/v1/posts/{id}/vote # Upvote/downvote
// Certificate APIs
GET /api/v1/certificates/{id} # Get certificate
GET /api/v1/verify/{credentialId} # Verify certificate (public)
// Payment APIs
POST /api/v1/payments/intent # Create payment intent
POST /api/v1/webhooks/stripe # Stripe webhook handler
// Live Classroom APIs
POST /api/v1/rooms/{id}/join # Join live room
GET /api/v1/rooms/{id}/participants # List participants
// Recommendation APIs
GET /api/v1/recommendations # Personalized recommendations
GET /api/v1/search?q={query} # Semantic course searchREST API
26. Testing Strategy
Test Pyramid
(Cypress/Playwright)
50 tests, 15 min"] INT["Integration Tests
(Testcontainers)
200 tests, 5 min"] UNIT["Unit Tests
(xUnit + Moq)
2000+ tests, 30 sec"] E2E -.-> INT -.-> UNIT
Testing Categories
| Category | Tool | Coverage Target | Execution |
|---|---|---|---|
| Unit Tests | xUnit + FluentAssertions | 85% line coverage | Every PR (CI) |
| Integration Tests | Testcontainers (PostgreSQL, Redis, Kafka) | All service boundaries | Every PR (CI) |
| Contract Tests | Pact | All inter-service APIs | Nightly build |
| E2E Tests | Playwright | Critical user journeys | Nightly + pre-deploy |
| Load Tests | k6 | Meet p99 SLOs | Weekly |
| Security Tests | OWASP ZAP + Snyk | Zero critical CVEs | Weekly |
| Chaos Tests | Chaos Monkey | Failure scenarios | Monthly |
| Accessibility | axe-core | WCAG 2.1 AA | Every PR |
Critical User Journey Tests
// E2E Test: Full Course Lifecycle
[Fact]
public async Task Student_Completes_Course_And_Receives_Certificate()
{
// 1. Register new student
var student = await _client.RegisterAsync(new RegisterRequest
{
Email = "test@example.com",
Password = "SecureP@ss123"
});
// 2. Browse catalog and enroll
var courses = await _client.SearchCoursesAsync("System Design");
var enrollment = await _client.EnrollAsync(courses.First().Id);
// 3. Watch video lessons
foreach (var lesson in enrollment.Course.Lessons)
{
var session = await _client.GetVideoSessionAsync(lesson.VideoId);
await _client.UpdateProgressAsync(lesson.VideoId, lesson.Duration);
}
// 4. Complete quizzes
foreach (var quiz in enrollment.Course.Quizzes)
{
var attempt = await _client.StartQuizAsync(quiz.Id);
await _client.SubmitQuizAsync(attempt.Id, GenerateAnswers(quiz));
}
// 5. Submit and pass final assignment
var assignment = enrollment.Course.FinalAssignment;
await _client.SubmitAssignmentAsync(assignment.Id, "solution.zip");
await _client.WaitForGradingAsync(assignment.Id, Timeout);
// 6. Verify certificate
var cert = await _client.VerifyCompletionAsync(enrollment.CourseId);
Assert.NotNull(cert);
Assert.NotEmpty(cert.VerificationUrl);
// 7. Verify certificate is publicly verifiable
var publicCert = await _client.PublicVerifyAsync(cert.CredentialId);
Assert.Equal(student.FullName, publicCert.StudentName);
}C#
27. Interview Q&A
Q1: How do you handle video streaming at scale for millions of concurrent viewers?
Q2: How do you design the grading system to handle rubric-based assessments and peer reviews?
Q3: How do you ensure SCORM/xAPI compliance?
Q4: How do you handle FERPA compliance for student data?
Q5: How would you design the adaptive learning path system?
Q6: How do you handle the cost of serving petabytes of video content?
Q7: How do you prevent plagiarism in code assignments?
Q8: Design the real-time progress tracking system for 10M users.
Q9: How do you design the live virtual classroom for 5,000 students?
Q10: How do you handle enrollment payments and refunds?
Q11: How would you handle a sudden 10x spike in traffic (e.g., a viral course launch)?
Q12: How do you ensure accessibility for students with disabilities?
28. References
- Architecting for Scale — Lee Atchison (O'Reilly)
- Designing Data-Intensive Applications — Martin Kleppmann
- Coursera Engineering Blog: Scaling Video Delivery
- HLS Specification — Apple Developer Documentation
- WebRTC for the Curious — Powered by Afonso and others
- xAPI Specification — ADL Initiative
- SCORM 2004 4th Edition — ADL
- LTI 1.3 Specification — IMS Global / 1EdTech
- FERPA Regulations — U.S. Department of Education
- GDPR Text — European Commission
- Netflix Tech Blog: Adaptive Bitrate Streaming
- MediaSoup SFU Documentation
- AWS MediaConvert Best Practices