List several items, one is fake/invalid. Often uses :
<slot name="question">
<div className="question">
Which of these is <em class="highlight">NOT</em> ❌ a valid PostgreSQL type?
(Seriously, these are (mostly) real types.)
</div>
</slot>
Options: 5-7 real items + 1 plausible-sounding fake. These questions typically have more options (5-8).
3. "Which Syntax is Correct?"
Show variations of syntax, ask which works:
<slot name="question">
<div className="question">
How are variables defined in Bash?
</div>
</slot>
Options: Correct syntax + common mistakes (spaces, wrong operators, etc.)
4. "What Happens When..." (Behavior Prediction)
Describe a scenario, ask about runtime/compile behavior:
<slot name="question">
<div className="question">
What happens with multiple mutable references?
```rust
fn main() {
let mut wisdom = String::from("He who laughs at");
let ref1 = &mut wisdom;
let ref2 = &mut wisdom;
ref1.push_str(" himself never runs");
ref2.push_str(" out of things to laugh at.");
}
Think about Rust's rules for mutable references.
```
5. "Conceptual Knowledge" (Theory Questions)
Ask about concepts, best practices, or when to use something:
<slot name="question">
<div className="question">
When should you use Rc (Reference Counting) in Rust?
</div>
</slot>
Option Design Patterns
Number of Options
- 4-6 options is typical for "what's the output" and conceptual questions
- 5-8 options for "spot the invalid" / type identification questions
- 4-5 options for output prediction questions
Option Structure
{ text: 'Option text' }
{ text: 'Option text', isAnswer: true }
{ text: 'Wrong answer', hint: "Think about X..." }
{ text: 'TypeScript error', isAnswer: true }
{ text: 'null', isAnswer: true }
Crafting Wrong Answers
Wrong answers should be:
- Plausible misconceptions: What a developer might actually think
- Common mistakes: Real errors people make (e.g.,
name = Dan with spaces in Bash)
- Close-but-wrong: Values that differ from correct by one character or concept
- Opposite of correct: For conceptual questions
Examples of good wrong answers:
- For
JSON.stringify(new Error('Oops')): '{"message":"Oops","name":"Error"}' — what you'd expect but isn't true
- For Bash variable:
"$name=Dan" — common mistake of using $ prefix
- For CSS invalid selector:
c {} — looks valid but <c> isn't an HTML element
- For PostgreSQL types:
ipv4 — so intuitive it must exist, but doesn't; inet covers both IPv4 and IPv6
Answer Distribution
- Exactly ONE
isAnswer: true per question (unless intentionally multi-answer — see below)
- Spread correct answers across different positions (don't always make it option B or C)
- Don't make "Error" or "undefined" always the correct answer
Multiple Correct Answers (RARE)
Only use when there are genuinely two valid answers. Explain why in the explanation:
options={[
{ text: 'TypeScript error', isAnswer: true },
{ text: "'null'", isAnswer: true },
{ text: 'undefined' },
]}
This was used in the Destructuring quiz for a TypeScript question where ignoring type errors gives different runtime behavior. Use sparingly — most questions should have exactly one correct answer.
Hint Writing Guidelines
Per-Option Hints (Preferred Style)
Attached to wrong-answer options. Shown as tooltips when user clicks that wrong answer after the first wrong attempt (IGNORE_HINTS = 1).
options={[
{ text: 'Wrong A', hint: "Think about what happens to 'philosopher' after it's moved" },
{ text: 'Compilation Error', isAnswer: true },
{ text: 'Runtime Error', hint: "Rust catches these issues at compile time" },
{ text: 'null', hint: "Rust doesn't have null values" },
]}
Hint writing principles:
- 1-2 sentences max
- Point to the concept, not the answer
- Use patterns: "Think about...", "Consider...", "Remember..."
- Can reference specific technical terms ("enumerable properties", "prototype chain")
- Witty/humorous hints are acceptable and encouraged (AWS quiz:
{ text: "DynamoDB credits for Green Vendors", hint: "Really?" })
Hints Slot (Legacy Style)
Used in older JavaScript quizzes (2024 era). Can be empty or contain a single hint div:
<!-- Empty hints slot (no hint for this question) -->
<slot name='hints'>
</slot>
<!-- With hint content -->
<slot name='hints'>
<div className="hint">
Think about enumerable properties on Error objects.
</div>
</slot>
Explanation Writing Guidelines
Structure
- Direct answer first: State the correct answer immediately
- Explain why: Break down the reasoning
- Show alternatives: "Here's how to fix this..." or "Common patterns include..."
- Provide context: When/why this matters in real code
- Link to resources: MDN, official docs, etc. (optional but common)
Explanation Styles by Depth
Short (2-4 sentences, for straightforward questions):
<slot name="explanation">
<div className="explanation">
The `age` property does not exist on `person`, so `age` will be `undefined`.
Definitely not `Infinity` 😅
This results in:
```plaintext
Name: Dan Levy, Age: undefined
```
Medium (1-2 paragraphs, for intermediate questions):
<slot name="explanation">
<div className="explanation">
Error objects have non-enumerable properties (`message`, `name`, `stack`), so
`JSON.stringify()` returns `{}`. This is a common gotcha when sending errors
in API responses. Use `JSON.stringify(error, Object.getOwnPropertyNames(error))`
or create a plain object instead.
</div>
</slot>
Long (multiple paragraphs + code examples, for advanced/Rust topics):
<slot name="explanation">
<div className="explanation">
This code fails to compile because of Rust's ownership rules. When we assign
`philosopher` to `greeting`, the ownership of the String is moved to `greeting`.
After this move, `philosopher` is no longer valid to use.
Here are three ways to fix this:
1. Clone the string (creates a new copy):
```rust
let greeting = philosopher.clone();
- Use a reference (borrows the value):
let greeting = &philosopher;
- Use a string slice (borrows part of the string):
let greeting = &philosopher[..];
Each solution has different use cases and performance implications.
```
Explanation Content Rules
- Always use fenced code blocks with language hint (
js`, sql, ````rust, bash`, html, ````plaintext)
- Show correct code and explain why it works
- Show common fixes for error-based questions
- Use bullet lists for enumerating options/features
- Include MDN or official doc links for web platform topics
- Personality is welcome (see PostgreSQL quiz: "Did this make you frustrated, even angry?")
Links in Explanations
[Learn more about RegExp flags](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags)
Common link targets:
- MDN Web Docs (JavaScript, CSS, HTML)
- Official language docs (Rust Book, PostgreSQL docs)
- BashFAQ, Greg's Redirection FAQ
- Relevant blog posts on the same site
Group Naming Conventions
Groups organize questions into thematic sections displayed in the quiz UI.
Progression Pattern
"Warmup" → "Basic [Topic]" → "[Topic]" → "Advanced [Topic]" → "[Topic] Gotchas"
Examples from Existing Quizzes
JavaScript Error Quiz — descriptive/thematic names:
- "Serialization Surprises", "Type Checking Tricks", "Throwing Non-Errors", "Custom Errors", "Error Cause", "Stack Traces", "Message Templates", "API Gotchas", "Async Errors", "Error Properties", "Error Boundaries"
PostgreSQL Quiz — topic + prefix pattern:
- "Warmup: Functions", "Warmup: Type Casting", "Constraints", "Date/Time", "Timestamps", "Postgres Types", "Integer Arithmetic"
Regex Quiz — skill-level progression with humor:
- "Warmup" → "Basic Matching" → "Common Gotchas" → "Look-ahead" → "Look-behind" → "Look-into-hell" 😈
Bash Quiz — concise topic names:
- "Warmup", "Warmup: Escaping", "Warmup: Expansion", "Variables", "Replacing Substrings", "String Length", "Conditionals", "Functions", "Composition", "Arithmetic", "Loops", "Gotchas"
Rust Quiz — conceptual groupings:
- "Ownership", "Borrowing", "Lifetime Elision", "Smart Pointers", "Reference Counting", "Lifetimes", "RefCells", "Mutability", "Memory Patterns", "Design Patterns", "Best Practices", "Advanced Patterns"
Group Naming Rules
- Start with "Warmup" for 1-3 easy introductory questions
- Use topic names for thematic groupings (2-5 questions per group)
- Add prefixes like "Warmup:", "Advanced:", "Common:" for clarity
- Be creative — names that fit the topic make it memorable ("Serialization Surprises", "Look-into-hell")
- Don't repeat group names within a quiz (each group should have a unique name)
Difficulty Progression
Standard Progression
- Warmup (1-3 questions): Easy, foundational concepts — build confidence
- Basic/Intermediate: Core concepts with some gotchas
- Advanced: Tricky edge cases, cross-environment behavior
- Expert/Gotchas: Rare behaviors, "gotcha" questions, mind-benders
Numeric Difficulty Scale (Rust & AWS Quizzes)
Used with the difficulty prop on a 1-5 scale:
| Level | Meaning | Example |
|---|
| 1 | Warmup/Trivia | "What does S3 stand for?" |
| 2 | Basic | "Basic variable declaration in Bash" |
| 3 | Intermediate | "Mutable borrowing rules" |
| 4 | Advanced | "Lifetime annotations on structs" |
| 5 | Expert | "Zero-cost abstractions / memory layout" |
Difficulty + Objectives Pattern
Used in Rust and AWS quizzes for pedagogical structure:
<Challenge
client:load
index={0}
group="Ownership"
title="Basic Move Semantics"
difficulty={2}
objectives={[
"Explain Rust's ownership rules and move semantics",
"Identify compilation errors related to moved values",
"Apply solutions to fix move-related compilation errors"
]}
>
When to use difficulty + objectives: Add them when the quiz covers a structured learning progression (Rust, AWS, databases). Skip for casual knowledge quizzes (regex, CSS, destructuring).
Code Formatting Rules
Code Block Language Hints
Always specify the language:
```js // JavaScript
```ts // TypeScript
```sql // SQL / PostgreSQL
```rust // Rust
```bash // Shell / Bash
```html // HTML
```css // CSS
```json // JSON
```plaintext // Plain output / no syntax
Code Width
- Keep code lines under ~50-60 characters when possible (especially for Rust quizzes, which note this explicitly)
- This ensures readability across mobile devices
- Break long lines at natural points (after operators, at function args)
Inline Code
Use backticks for:
- Variable names:
err.name
- Function calls:
JSON.stringify()
- Operators:
??, ?., ||=
- Values:
undefined, null, true
- CSS properties:
font-size, align-content
- SQL types:
VARCHAR(100), timestamptz
Intro Section Patterns
Standard Intro
<p class="inset">Ready to test your [topic] skills? [emoji]</p>
This quiz covers [scope description]: from [basic concept] to [advanced concept].
Good luck! 🍀
Bullet Point Intro (Very Common)
### Think you know [topic] inside and out?
* **Test your expertise!** 💥
* No login or signup required. ✨
* Multiple choice. 🤖 ... _These ain't your typical questions!_
Playful Intro
<p class="inset">Or is it your <em>Symphony of Destruction?</em></p>
This quiz will test your knowledge of [topic]: from "basic" [concept] to [advanced concept].
Jump right in to the warmup — prove your skills! 👇
Multi-Part Quiz Intro
> **Part 1 of 2.** [Go to Part 2](/quiz-postgres-sql-mastery-pt2/)
<p class="inset">PostgreSQL 🐘 Is easily my favorite Database!</p>
This quiz covers a mix of familiar and lesser-known features...
With Context Intro (AWS/Rust style)
<p class="inset">Ready to test your Rust memory management skills? 🦀</p>
This quiz will challenge your understanding of Rust's ownership system, borrowing rules, lifetimes, and smart pointers.
**Note:** The questions are formatted in ~50-column width to ensure readability across all devices.
Whether you're a seasoned Rustacean or just getting started, this quiz will help reinforce your knowledge. **Let's dive in!** 🦀
Section Breaks Between Groups
Use markdown headers or styled paragraphs to break up long quizzes:
# Dizzying Types
I'm sure you've used _so many_ types in PostgreSQL, right?
The next few questions are about Postgres' v17 native types. 🤯
For each question, identify the **one invalid type**. 🕵️♂️
<p class="inset">Onward!</p>
# TypeScript Ahead
Section breaks serve as transitions — add context about what's coming next.
Closing Section Patterns
Standard Closing
## Master the Art of Error Handling
From serialization gotchas to cross-context instanceof failures, these advanced concepts separate junior developers from ~seasoned~ damaged professionals.
Ready for more challenges? Check out our [complete quiz collection](/challenges/) for additional brain teasers on JavaScript, algorithms, and more!
Playful Closing
<p className="inset">Did my Bash Quiz leave you in shambles?</p>
Let me know in the comments below!
### Further Reading
Brush up on your skills:
- [Bash Guide](https://www.gnu.org/software/bash/manual/bash.html)
- [BashFAQ](http://mywiki.wooledge.org/BashFAQ)
- [ShellCheck](https://www.shellcheck.net/)
Multi-Part Closing
Well done! You went deep on several areas of PostgreSQL! 🐘
I hope you learned something new, or at least got a score to gloat about! 🏆
<p class="inset">Check out [Part 2](/quiz-postgres-sql-mastery-pt2/) for more Postgres fun! 🚀</p>
Want more thrills in life? Check out my [Quiz Collection](/challenges/) for endless* fun!
Rust-Style Closing (with resources)
Thanks for taking the quiz! If you enjoyed testing your Rust knowledge, check out my other [programming challenges](/challenges/)! 🧠
**Want to level up your Rust skills?** Here are some recommended resources:
- [Rust Book - Chapter 4: Ownership](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html)
- [Rust By Example - Memory Management](https://doc.rust-lang.org/rust-by-example/scope.html)
All closings should include a link to /challenges/ (the quiz collection page).
Verification Steps
After writing a quiz, verify:
1. Frontmatter Validation
2. Challenge Structure
3. Content Quality
4. Formatting
5. Run Validation Tools
bun run check
bun run fix-quizzes
bun run build
bun run screenshots
6. Generate Social Images
bun run screenshots
Common Gotchas
DON'T
- ❌ Use
getCollection("posts") directly — use PostCollections from @/shared/postsCache
- ❌ Manually edit
public/_redirects — use frontmatter redirects array
- ❌ Put multiple
isAnswer: true in one question (unless intentional and explained)
- ❌ Write explanations that just repeat the question
- ❌ Make wrong answers obviously wrong
- ❌ Use emojis excessively (1-3 per section max)
- ❌ Forget the
client:load directive on Challenges
- ❌ Skip the warmup questions
- ❌ Write code that doesn't actually work as described
- ❌ Use
class in JSX — use className instead
- ❌ Use
client:only="react" (legacy, use client:load)
DO
- ✅ Start with warmup questions (1-3 easy ones)
- ✅ Group related questions together with meaningful group names
- ✅ Include real-world context in explanations
- ✅ Show multiple solutions when applicable (especially for Rust)
- ✅ Link to authoritative resources
- ✅ Use personality and humor appropriately
- ✅ Test code examples before including them
- ✅ Run
bun run fix-quizzes before committing
- ✅ Use
className not class in JSX/MDX
- ✅ Link to
/challenges/ in closing sections
File Structure
src/content/posts/YYYY-MM-DD--quiz-[topic-slug]/
├── index.mdx # Quiz content
├── [cover]-wide.webp # Full width cover image
├── [cover]-square-200.webp # Mobile cover (200px)
└── [cover]-square-300px.webp # Icon cover (300px, optional)
Some quizzes also include:
desktop-social.webp — social sharing image
mobile.webp — mobile social sharing image
annotated-code/ — subdirectory with annotated code images
Directory Naming
Use the pattern: YYYY-MM-DD--quiz-[descriptive-slug]
Examples:
2025-11-04--quiz-advanced-js-error-mastery
2024-11-27--quiz-postgres-sql-mastery-pt1
2024-11-15--quiz-regex-or-wreckage
2024-12-28--quiz-is-your-memory-rusty
2024-11-20--quiz-bash-in-the-shell
Import Paths
The import paths are relative to the post's location:
import Challenge from '../../../components/QuizUI/Challenge';
import QuizUI from '../../../components/QuizUI/QuizUI';
These paths assume posts are in src/content/posts/YYYY-MM-DD--slug/ (3 levels deep from src).
Advanced Challenge Features
Per-Option Hints (Preferred)
Hints attached to individual wrong answers, shown after the first incorrect attempt:
options={[
{ text: 'Wrong A', hint: "Think about what happens after a move" },
{ text: 'Compilation Error', isAnswer: true },
{ text: 'Runtime Error', hint: "Rust catches these at compile time" },
]}
Hint behavior: After 1 wrong attempt (IGNORE_HINTS = 1), subsequent wrong selections that have hints will show the hint as a tooltip. This means the first wrong answer never shows its hint — only the second+ wrong answers do.
Difficulty + Objectives (Rust & AWS Pattern)
For structured learning quizzes, add difficulty and objectives:
<Challenge
client:load
index={0}
group="Ownership"
title="Basic Move Semantics"
difficulty={2}
objectives={[
"Explain Rust's ownership rules and move semantics",
"Identify compilation errors related to moved values",
"Apply solutions to fix move-related compilation errors"
]}
>
When to use: Add these for quizzes that teach a structured curriculum (Rust, AWS, databases). Skip for casual knowledge quizzes (regex, CSS, destructuring).
Multiple Correct Answers (RARE)
Some questions intentionally have multiple correct answers:
options={[
{ text: 'TypeScript error', isAnswer: true },
{ text: 'null', isAnswer: true },
{ text: 'undefined' },
]}
When to use: Only when there are genuinely two valid answers (e.g., TypeScript says one thing, runtime says another). Always explain in the explanation why there are two correct answers.
Question Count Guidelines
- Minimum: 9 questions (oldest quiz has 9, most have 10-16)
- Typical: 12-16 questions
- Maximum: ~18 questions (Rust quiz has 18, AWS has 20+)
- Beyond ~18-20: Split into Part 1, Part 2, etc. with cross-links
For multi-part quizzes, link between parts:
> **Part 1 of 2.** [Go to Part 2](/quiz-postgres-sql-mastery-pt2/)
Tag Guidelines
Always include these tag categories:
- Quiz identifier:
quiz
- Topic:
javascript, rust, css, postgresql, bash, regex, aws, cloud
- Sub-topic (optional):
error-handling, memory-management, destructuring, s3, dynamodb
- Difficulty levels:
intro, beginner, intermediate, advanced, expert
Examples:
tags: [quiz, javascript, error-handling, intermediate, advanced]
tags: [quiz, javascript, intro, esnext, features, intermediate]
tags: [quiz, rust, memory-management, ownership, borrowing, lifetimes, intermediate, advanced]
tags: [quiz, aws, cloud, storage, databases, s3, dynamodb, rds, elasticache]
tags: [quiz, bash, scripting, shell, linux, beginner, intermediate, advanced]
Content Schema (Zod)
The frontmatter is validated against this Zod schema (from src/content.config.ts):
z.object({
title: z.string(),
subTitle: z.string().optional().default(""),
label: z.string().optional(),
social_image: image().optional(),
commentsKeyOverride: z.string().optional(),
publish: z.boolean().optional().default(true),
draft: z.boolean().optional(),
unlisted: z.coerce.boolean().optional(),
hidden: z.coerce.boolean().optional(),
date: z.coerce.string().optional(),
modified: z.coerce.string().optional(),
minReleaseDate: z.coerce.date().optional(),
cover: image().optional(),
cover_full_width: image().optional(),
cover_mobile: image().optional(),
cover_icon: image().optional(),
category: z.string().optional(),
subCategory: z.string().optional(),
tags: z.array(z.string()).optional(),
popularity: z.number().min(0).max(1.0).optional(),
related: z.array(z.string()).optional(),
redirects: z.array(z.string()).optional(),
})
Key observations:
title is the only truly required field (everything else has defaults or is optional)
- But for quizzes, you should always include:
title, category: Quiz, subCategory, date, tags, and cover images
subTitle defaults to "" — set it to something catchy