Personality quizzes love putting people in boxes: "You're an introvert!" But the most widely used model in personality psychology, the Big Five (openness, conscientiousness, extraversion, agreeableness, neuroticism), describes continuums, not types. Everyone sits somewhere on each scale.
The Quiz Hub hosts free psychology quizzes and personality tests, including a Big Five test. Its results are designed to show where you sit on a continuum rather than hand you a binary label. Here's how to score a Big Five-style questionnaire correctly — including the part most hobby implementations get wrong.
The item model
Each question (item) measures one trait and is either positively keyed ("I am the life of the party" → extraversion) or reverse keyed ("I don't talk a lot" → also extraversion, but agreeing means less of it).
type Trait = "O" | "C" | "E" | "A" | "N";
interface Item {
id: string;
text: string;
trait: Trait;
reverse: boolean;
}
// Responses on a 1–5 Likert scale: strongly disagree … strongly agree
type Answers = Record<string, 1 | 2 | 3 | 4 | 5>;
Reverse scoring
This is the bug that ruins most homemade personality tests: summing raw answers without flipping reverse-keyed items. On a 1–5 scale, reverse scoring is 6 - value:
const scoreItem = (item: Item, value: number) => (item.reverse ? 6 - value : value);
Reverse-keyed items exist to reduce "acquiescence bias" — the tendency some people have to agree with everything. If you skip the flip, those people get nonsense results.
Trait scores as a 0–100 position
Average each trait's item scores, then map to 0–100 so it's easy to show on a slider:
export function traitScores(items: Item[], answers: Answers) {
const acc: Record<Trait, number[]> = { O: [], C: [], E: [], A: [], N: [] };
for (const item of items) {
const v = answers[item.id];
if (v === undefined) continue;
acc[item.trait].push(scoreItem(item, v));
}
return Object.fromEntries(
Object.entries(acc).map(([t, vals]) => {
if (vals.length === 0) return [t, null];
const mean = vals.reduce((a, b) => a + b, 0) / vals.length; // 1..5
return [t, Math.round(((mean - 1) / 4) * 100)]; // 0..100
})
) as Record<Trait, number | null>;
}
Returning null for an unanswered trait is better than returning 0 — "we can't tell" isn't the same as "very low".
Present a position, not a verdict
Rather than "You are an extravert," show the score on a labeled bar with descriptive anchors at both ends. Language matters too: "You scored toward the reserved end of extraversion" is more accurate — and kinder — than "You are antisocial."
function describe(score: number, low: string, high: string) {
if (score < 35) return `toward the ${low} end`;
if (score > 65) return `toward the ${high} end`;
return `near the middle, between ${low} and ${high}`;
}
Consistency checks
Pairs of opposite items (one positive, one reverse, same trait) should agree after scoring. Large disagreements across several pairs can indicate random clicking — worth a gentle "your answers look inconsistent" note rather than a confident result.
What results are (and aren't) for
The site frames results the right way: they can name tendencies you already notice, give you vocabulary to discuss patterns with a partner or therapist, or place you on a continuum for fun discussion. Where a quiz touches on mood, a high score is a prompt to talk to a GP or counsellor — not a diagnosis.
Takeaways
- Always reverse-score reverse-keyed items.
- Report traits as positions on a continuum, not types.
- Treat missing data as missing, not zero.
- Use neutral language and be clear about what a quiz can't tell you.
Try the tests at thequizhub.org. Have you implemented psychometric scoring before? I'd like to hear how you handled validation.
Top comments (0)