Online Exam Software: Question Banks, Timers and Anti-Cheating That Works
Founder & Lead Engineer, RAITHub
Online exam software needs five parts: a tagged question bank, per-student randomisation, a timer the server enforces, autosave, and grading with an audit trail. Anti-cheating is weaker than most sales pages suggest; Moodle's own documentation says browser security measures "are not fool-proof" (Moodle: quiz settings). And plan for the spike: 2,000 students autosaving every 20 seconds is 100 writes a second.
If you would rather have it built for you, see how RAITHub would build this below.
This guide is for EdTech founders, coaching and test-prep businesses, training companies and universities planning their own exam platform. RAITHub has not shipped a standalone online exam product. Its education work is PadhAI, an AI tutoring platform, and the guidance below on timers, question banks and proctoring is engineering advice, labelled as such.
What does online exam software need to do?
Deliver the right questions to the right student, for the right length of time, save every answer, and grade it in a way you can defend later. Everything else, from leaderboards to AI proctoring, sits on top.
| Part | What it does | What goes wrong when it is weak |
|---|---|---|
| Question bank | Questions tagged by subject, topic, difficulty and type, with versions | A corrected question silently changes the marks of students who already sat it |
| Exam builder | Rules such as "10 easy and 5 hard algebra questions", windows, attempts | Every student gets the same paper in the same order, and answers travel by WhatsApp |
| Timer | Start, deadline, auto-submit, extra time for students who are entitled to it | A student changes their laptop clock, or a refresh resets the timer |
| Autosave | Answers saved to the server as the student works | A dropped connection at minute 55 loses the whole attempt |
| Grading and results | Automatic marking, manual marking for written answers, moderation, release | A key error found after results are out, with no way to regrade cleanly |
| Integrity controls | Lockdown browser checks, randomisation, logs, optional proctoring | Expensive tools that flag innocent students and miss the phone under the desk |
Should you buy, configure or build online exam software?
| Option | Example | Choose this when | Watch out for |
|---|---|---|---|
| Off-the-shelf tool | Testmoz: free plan with "50 questions and 100 results per test", Standard $50 a year with unlimited tests and results, and "up to 300 simultaneous test takers" before you need a custom plan (Testmoz pricing) | Classroom quizzes and low-stakes tests for a few hundred students at a time | Concurrency caps on exam day; limited branding and data export |
| Open-source platform, configured | Moodle quiz: time limits, auto-submit on expiry, and Safe Exam Browser integration (Moodle docs) | You already run Moodle, or want a mature quiz engine and can host and maintain it | Hosting, upgrades and tuning for exam-day load are yours |
| Custom build | Your own exam engine, as a web app and PWA | Exams are your product, you sell to many institutes, you need your own question types, payments or local channels, or you need to scale past a tool's cap | You own reliability; the timer, autosave and grading need the strictest tests you write |
If you already have Moodle, start there; the custom LMS vs Moodle guide covers when that stops working. Build when the exam is what customers pay you for.
How should a question bank and randomisation work?
Store questions as versioned records, build exams from rules rather than fixed lists, and fix each student's paper on the server when the attempt starts.
- Version every question. An attempt records the exact question version it showed. Fixing a typo creates version 2; students who sat version 1 are graded against version 1, or regraded deliberately with a logged reason.
- Pools, not lists. "Pick 5 from this pool of 30 medium-difficulty questions" gives each student a different paper of similar difficulty. A pool of 5 for 5 slots is just a shuffled paper.
- Shuffle options, carefully. Shuffling answer choices is cheap and useful, except for "all of the above" options, which need to be pinned.
- Parameterised questions. For maths and science, generate numbers per student ("a train travels 140 km in 2.5 hours") and compute the answer on the server. Shared answers are then useless.
- Fix the paper at start. Draw the questions and their order once, store them on the attempt, and serve that order on every reload. Re-drawing on refresh lets a student fish for easier questions.
How do you build an exam timer the student can't cheat?
The server owns the clock. The browser shows a countdown, but the deadline is stored on the server when the attempt starts, and every save and submit is checked against it. Changing the laptop clock or refreshing the page changes nothing.
This is the same model Moodle's quiz uses, with three choices when time runs out: "Open attempts are submitted automatically", a grace period in which "open attempts can be submitted, but no more questions answered", or "Attempts must be submitted before time expires, or they are not counted" (Moodle: quiz settings). A minimal TypeScript sketch written for this post:
type Attempt = {
id: string
examId: string
studentId: string
startedAt: Date
deadline: Date
submittedAt: Date | null
}
interface Db {
getExam(id: string): Promise<{ opensAt: Date; closesAt: Date; durationMs: number }>
findAttempt(examId: string, studentId: string): Promise<Attempt | null>
insertAttempt(a: Omit<Attempt, 'id' | 'submittedAt'>): Promise<Attempt> // UNIQUE (exam_id, student_id)
getAttempt(id: string): Promise<Attempt>
upsertAnswer(a: { attemptId: string; questionId: string; answer: unknown; clientSeq: number }): Promise<void>
}
class HttpError extends Error {
constructor(public status: number, message: string) { super(message) }
}
const GRACE_MS = 5_000 // allowance for a save that left the browser just before the deadline
export async function startAttempt(db: Db, examId: string, studentId: string, extraMs = 0, now = new Date()) {
const exam = await db.getExam(examId)
if (now < exam.opensAt || now >= exam.closesAt) throw new HttpError(403, 'exam is not open')
const existing = await db.findAttempt(examId, studentId)
if (existing) return existing // a reload resumes; it never restarts the clock
const end = Math.min(now.getTime() + exam.durationMs + extraMs, exam.closesAt.getTime())
return db.insertAttempt({ examId, studentId, startedAt: now, deadline: new Date(end) })
}
export async function saveAnswer(
db: Db, attemptId: string, questionId: string, answer: unknown, clientSeq: number, now = new Date(),
) {
const attempt = await db.getAttempt(attemptId)
if (attempt.submittedAt) throw new HttpError(409, 'attempt already submitted')
if (now.getTime() > attempt.deadline.getTime() + GRACE_MS) throw new HttpError(410, 'time is up')
await db.upsertAnswer({ attemptId, questionId, answer, clientSeq })
}
Four details make it hold up:
- One attempt per student is a database constraint, a unique key on exam and student, so two tabs or a double click cannot start two attempts.
- Autosaves can arrive out of order. The upsert keeps the answer with the highest
clientSeq, a counter the browser increments, so a slow older save never overwrites a newer one. - Auto-submit runs on the server. A scheduled job closes attempts whose deadline has passed, so a student who closes the laptop still gets a submitted attempt with everything saved.
- Extra time is data. Students entitled to extra time get it through
extraMsat start, recorded on the attempt, not by a teacher editing a deadline by hand.
Also check that the logged-in user owns the attempt on every request, and rate-limit saves per attempt; the API rate limiting guide covers the patterns.
How should autosave and submission work on a bad connection?
Save each answer on change and on a short interval, queue saves in the browser while offline, and tell the student plainly when the last save succeeded. "Saved 4 seconds ago" removes most exam-day panic calls.
Keep each save small, one question's answer, not the whole paper. Store the queue in the browser's local storage so a refresh does not lose unsent answers, and replay it with the same clientSeq values when the connection returns. Saves that arrive after the deadline plus grace are rejected and logged, which gives you evidence if a student disputes a lost answer.
How should grading, results and regrading work?
Grade from stored answers against the stored question version, keep manual marks separate from automatic ones, and release results only when someone with authority presses release.
- Automatic marking for multiple choice, numeric answers with a tolerance, and exact-match short answers. For computed answers, check them in code rather than by string comparison; PadhAI, the AI tutoring platform RAITHub built, uses math verification so a mathematical result is checked rather than trusted.
- Manual marking for written answers, with blind marking where possible and a second marker for borderline scores.
- Regrading as an event. When a key is wrong, record the change, the reason and who approved it, then recompute. The audit log design guide covers the pattern.
- Grading runs in the background. Submissions are saved instantly; marking and rank calculation run as queued jobs, so a submit spike does not slow the exam for students still writing.
Which anti-cheating measures work, and which are theatre?
Measures that change what a cheater can gain work. Measures that try to watch a student's room through a browser help less, cost more, and produce false alarms. Moodle's documentation puts it plainly: "There is a limit to what the quiz, which runs on a web server, can do to restrict what the student sitting at their computer can do while attempting the quiz" (Moodle: quiz settings).
| Measure | What it stops | What it does not stop | Verdict |
|---|---|---|---|
| Server-owned timer and one attempt per student | Clock tampering, extra attempts, restarts | Nothing it claims to | Works; build it first |
| Question pools and parameterised questions | Answer sharing between students | A student being helped live by someone else | Works, and costs only question-writing time |
| Safe Exam Browser lockdown | Other apps and websites on that computer | A phone, a second device, another person in the room | Useful for supervised rooms and managed laptops |
| Tab-switch or focus-loss detection | Little on its own; it records that focus left the page | A second device; it also fires on pop-up notifications | A weak signal, never evidence by itself |
| Disabling copy, paste and right-click | Casual copying of question text | A phone camera | Mostly theatre |
| Webcam or AI proctoring | Some impersonation and obvious behaviour | Determined cheating; it produces flags a human must review | Use only with human review, clear consent and a privacy check |
| In-person invigilation | Most of the above | Little | Still the answer for high-stakes exams |
Safe Exam Browser (SEB) is a serious tool, used this way. Its project describes it as software that "controls access to resources like system functions, other websites and applications and prevents unauthorized resources being used during an exam", and it is freeware for Windows, macOS and iOS (Safe Exam Browser overview). It does not stop a phone on the desk. Your server should also refuse exam requests that do not come from a correctly configured SEB. SEB sends an X-SafeExamBrowser-ConfigKeyHash header, and the server checks it by hashing the absolute URL, without the fragment, followed by the Config Key (SEB: Config Key):
import { createHash, timingSafeEqual } from 'node:crypto'
// configKey: the exam's Config Key as a hex string, from the SEB configuration.
export function sebRequestIsValid(absoluteUrl: string, header: string | undefined, configKey: string): boolean {
if (!header) return false
const url = absoluteUrl.split('#')[0]
const expected = createHash('sha256').update(url + configKey, 'utf8').digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(header.trim().toLowerCase())
return a.length === b.length && timingSafeEqual(a, b)
}
Remote proctoring involves recording students, often minors, in their homes. Consent, retention and data protection rules vary by country. This is general information; confirm with your adviser before you switch it on.
How do you handle exam-day traffic spikes?
Size for the exam hour, not the average day. An exam platform is quiet for weeks and then every student arrives in the same five minutes.
| Moment | Example load, 2,000 students | How to handle it |
|---|---|---|
| Start | 2,000 attempt starts in about 2 minutes | Open a lobby a few minutes early; draw papers in one query per student; cache exam metadata |
| Writing | Autosave every 20 seconds: about 100 small writes a second | One-question saves, a lean indexed table, connection pooling |
| Deadline | Most submits in the final minute, plus the auto-submit job | Submit is a status change; grading and ranks go to a queue |
| Results | Everyone refreshes the results page | Release in one step and serve results from a cached read model |
For comparison, Testmoz's standard capacity is "up to 300 simultaneous test takers" before you contact it about a custom plan (Testmoz pricing). Whatever you build, load-test the real exam pattern, many starts at once and many submits at once, against a staging copy before the first live exam, and keep a runbook for the person on call. The scaling from MVP to production guide covers the wider checklist, and the EdTech software development guide covers exam-time load across a whole learning product.
How long does it take to build an online exam system yourself?
For a developer comfortable with TypeScript and PostgreSQL, a first version with a question bank, rule-based papers, a server-owned timer, autosave and automatic marking of objective questions is roughly 4–8 weeks of focused work, by RAITHub's estimate. Manual marking, SEB checks and multi-institute tenancy add more.
The main risk is losing answers on exam day: a timer or autosave bug that only shows up under real load and a flaky connection. You cannot reschedule 2,000 students cheaply. Write automated tests for the clock edges (save 1 second before the deadline, 1 second after, after the grace period, from two tabs), and run a full mock exam with real devices before the real one. The regression testing guide covers keeping those tests in CI.
Why RAITHub for this
- Education work that shipped. PadhAI, built by RAITHub: an AI tutoring platform with 11 services (9 Node/TypeScript, 2 Python), a Socratic tutor with math verification and RAG, a 70/20/10 LLM router, PWA, WhatsApp and Telegram channels, and 9 payment gateways for paid tests in many markets.
- Testing is the method, not a phase. 750+ automated tests on TheSkinProof, the founder's own venture, 1,024 on PropDesk and 400+ on this site, gated in CI. Timer and grading code is where that discipline matters most.
- Load and abuse handled on small budgets. This site runs rate limiting without Redis and had its Neon database cost fixed, the same concerns as an exam platform that is idle most of the month.
- Honest scope. A standalone exam engine and SEB integration would be new work on your project, quoted as such. See the EdTech industry page for what RAITHub builds for education.
When you don't need us
- You run classroom quizzes for a few hundred students. A hosted tool such as Testmoz, or the quiz in an LMS you already have, will do.
- You already run Moodle. Configure its quiz and Safe Exam Browser before building anything.
- You need certified high-stakes testing, such as licensing or national entrance exams with legal requirements. Use an established testing provider and in-person centres.
- You need a native mobile exam app. RAITHub builds web apps and PWAs only.
- You want developers placed in your team by the hour. RAITHub does not offer staff augmentation, and delivery is in English.
If your current exam platform loses answers or falls over at the start of an exam, start with a fix rather than a rebuild.
How RAITHub would build this
Scope, first release:
- A versioned question bank with tags, pools and parameterised questions, plus bulk import.
- Rule-based exam papers fixed per attempt, with windows, attempts and extra time.
- A server-owned timer, offline-tolerant autosave and server-side auto-submit, covered by clock-edge tests.
- Automatic and manual marking, moderation, regrading with an audit trail, and controlled release of results.
- Optional Safe Exam Browser request checks for supervised exams, and a load test shaped like your largest exam.
Timeline: 4–6 weeks at fixed scope for that first release, the MVP development range. An exam engine sold to many institutes follows the SaaS development path; integrating an exam API into an existing product is a 6–12 week backend and API project.
What you receive: automated tests running in CI, handover docs and runbooks, including an exam-day runbook, and full IP in the code under an NDA signed before detailed discussion. Production and student data stay in your own cloud account; development uses synthetic data.
Next step: a free 15-minute technical audit, then a written fixed quote. RAITHub publishes no rates; the pricing page explains how quotes work. Book the free audit and bring your largest expected exam size, your question types and how exams are supervised today.
Frequently asked questions
What features does online exam software need?
A versioned question bank, rule-based papers with randomisation, a timer enforced by the server, autosave, automatic and manual grading, controlled results release and an audit trail. Integrity tools such as Safe Exam Browser and proctoring are optional layers on top.
How do you stop students cheating in an online exam?
Reduce what cheating gains: draw each paper from question pools, use parameterised questions, enforce the timer on the server and allow one attempt per student. Lockdown browsers help in supervised rooms. For high-stakes exams, in-person invigilation still works better than any browser tool.
Does Safe Exam Browser prevent all cheating?
No. Safe Exam Browser locks down the computer it runs on, blocking other apps and websites, and runs on Windows, macOS and iOS. It cannot see a phone, a second device or another person in the room. Moodle's documentation says such browser security measures are not fool-proof.
How do you make an exam timer that can't be changed?
Store the deadline on the server when the attempt starts and check every save and submit against server time. The browser countdown is display only. A reload resumes the same attempt, and a server job auto-submits attempts once the deadline passes.
How many students can an online exam system handle at once?
It depends on the build and hosting. As one published figure, Testmoz's standard capacity is up to 300 simultaneous test takers before a custom plan. A custom system should be load-tested against your largest exam, including the start and submit spikes.
How long does it take to build online exam software?
A focused first release with a question bank, timed papers, autosave and automatic marking typically takes 4–6 weeks at fixed scope with RAITHub. Manual marking workflows, lockdown browser checks and multi-institute tenancy are scoped separately.
Is AI proctoring worth it?
Only with care. AI proctoring produces flags, not proof, so a human has to review them, and it records students in their homes, which raises consent and privacy questions. Many exams get more integrity per unit of cost from better question design and a server-enforced timer.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.