1. Exam Overview
The MongoDB Associate Developer exam (exam code C100DEV) validates practical, hands-on knowledge of building applications with MongoDB. It's multiple-choice and scenario-based — expect questions that show you a document or query and ask what happens, rather than pure definitions.
2. MongoDB Fundamentals
MongoDB is a document database. Instead of rows in tables, data is stored as BSON documents (a binary superset of JSON) grouped into collections, inside a database.
Core Terminology
| SQL | MongoDB |
|---|---|
| Database | Database |
| Table | Collection |
| Row | Document |
| Column | Field |
| Primary Key | _id field |
| Join | Embedding or $lookup |
Sample Document
{
_id: ObjectId("64f1a2b3c4d5e6f7a8b9c0d1"),
name: "Ada Lovelace",
age: 28,
active: true,
skills: ["math", "programming"],
address: { city: "London", country: "UK" },
joined: ISODate("2024-01-15T00:00:00Z")
}
Common BSON Types
Know these for the exam — questions often ask which type a field should be:
| Type | Example |
|---|---|
| Double / Int32 / Int64 / Decimal128 | 3.14, NumberLong(10) |
| String | "hello" |
| Object | { a: 1 } |
| Array | [1, 2, 3] |
| ObjectId | ObjectId() — 12-byte unique id, default _id |
| Boolean | true / false |
| Date | ISODate() — stored as ms since epoch |
| Null | null |
_id field is immutable, indexed
automatically, and unique per collection. If you don't supply one on insert, the driver auto-generates an
ObjectId.3. CRUD Operations
Create
db.users.insertOne({ name: "Ada", age: 28 })
db.users.insertMany([{ name: "Ada" }, { name: "Alan" }])
Read
db.users.find({ age: { $gte: 18 } })
db.users.findOne({ name: "Ada" })
db.users.find({ skills: "math" }).sort({ age: -1 }).limit(5)
Common Query Operators
| Operator | Meaning |
|---|---|
$eq, $ne |
equal / not equal |
$gt, $gte, $lt, $lte |
comparison |
$in, $nin |
value in / not in array |
$and, $or, $not, $nor |
logical |
$exists |
field is present |
$regex |
pattern match |
$elemMatch |
array element matches multiple conditions |
Querying Arrays
Array queries are a heavily tested corner of CRUD, because the matching rules are less obvious than scalar
fields. Given { tags: ["red", "blue", "green"] }:
| Query | Meaning |
|---|---|
{ tags: "blue" } |
Matches if any element of the array equals "blue" — no special operator
needed. |
{ tags: ["red", "blue", "green"] } |
Matches only if the array is exactly this array, in this order. |
{ tags: { $all: ["red", "blue"] } } |
Array must contain all of these values, any order, extras allowed. |
{ tags: { $size: 3 } } |
Array has exactly 3 elements (cannot combine with range operators; no index can satisfy
$size directly). |
{ "tags.0": "red" } |
Matches the element at a specific index. |
{ scores: { $elemMatch: { $gt: 80, $lt: 90 } } } |
A single element satisfies all the conditions together (without
$elemMatch, MongoDB would allow different elements to satisfy each condition
independently). |
// Array of embedded documents
db.students.find({
grades: { $elemMatch: { subject: "Math", score: { $gte: 90 } } }
})
// Without $elemMatch this could wrongly match a doc where
// one grade element has subject "Math" and a DIFFERENT element has score >= 90
{ "grades.score": { $gte: 90 }, "grades.subject": "Math" } (two separate conditions) can match
a document even if no single array element satisfies both — because MongoDB checks each condition against
the array independently. $elemMatch is what forces "same element."Array Projection
// Return only the first 2 elements of an array field
db.students.find({}, { grades: { $slice: 2 } })
// Return only the array element(s) that matched the query
db.students.find(
{ grades: { $elemMatch: { subject: "Math" } } },
{ "grades.$": 1 }
)
Update
db.users.updateOne({ name: "Ada" }, { $set: { age: 29 } })
db.users.updateMany({ active: false }, { $set: { archived: true } })
db.users.updateOne({ name: "Ada" }, { $inc: { loginCount: 1 } })
db.users.updateOne({ name: "Ada" }, { $push: { skills: "AI" } })
replaceOne() replaces the whole document (except
_id), while updateOne() with $set only modifies named fields. The
exam likes to test this distinction.Updating Arrays
The exam tests these array update operators specifically — know what each does to duplicates and ordering:
| Operator | Behavior |
|---|---|
$push |
Appends a value (allows duplicates). Combine with $each to push multiple values at once.
|
$addToSet |
Appends a value only if it doesn't already exist (no duplicates). Also supports $each.
|
$pull |
Removes all array elements matching a condition. |
$pullAll |
Removes all instances of the exact listed values. |
$pop |
Removes the first (-1) or last (1) element. |
// Push multiple values, keep sorted, cap at 5 (a leaderboard-style update)
db.users.updateOne(
{ _id: 1 },
{ $push: { scores: { $each: [95, 88], $sort: -1, $slice: 5 } } }
)
// Add without duplicating
db.users.updateOne({ _id: 1 }, { $addToSet: { skills: "MongoDB" } })
// Remove all elements matching a condition
db.users.updateOne({ _id: 1 }, { $pull: { scores: { $lt: 50 } } })
// Remove the last element
db.users.updateOne({ _id: 1 }, { $pop: { scores: 1 } })
Positional Operators — Updating Specific Array Elements
| Operator | Updates |
|---|---|
$ (positional) |
The first array element that matched the query filter. |
$[] (all positional) |
Every element in the array. |
$[<identifier>] (filtered positional) |
Only elements matching a condition in arrayFilters. |
// $ — update the one grade element that matched
db.students.updateOne(
{ _id: 1, "grades.subject": "Math" },
{ $set: { "grades.$.score": 95 } }
)
// $[] — give every element a 10% bonus
db.students.updateOne(
{ _id: 1 },
{ $mul: { "grades.$[].score": 1.1 } }
)
// $[] — only bump grades below 60, using arrayFilters
db.students.updateOne(
{ _id: 1 },
{ $set: { "grades.$[elem].score": 60 } },
{ arrayFilters: [{ "elem.score": { $lt: 60 } }] }
)
Delete
db.users.deleteOne({ name: "Ada" })
db.users.deleteMany({ active: false })
Upsert
db.users.updateOne(
{ name: "Grace" },
{ $set: { age: 40 } },
{ upsert: true } // inserts if no match found
)
4. Indexes
Indexes let MongoDB scan a smaller structure instead of every document (a
COLLSCAN). Every collection has a default index on _id.
How Indexes Work: B-Trees
MongoDB's default storage engine (WiredTiger) implements every index as a B-tree (technically a B+tree variant). Understanding the shape of that structure explains almost every indexing rule the exam tests.
- A B-tree keeps keys sorted across a balanced tree of nodes, so it can find a value, a
range, or "the next value in order" in
O(log n)disk/memory reads instead of scanning every document. - Each entry in the tree stores the indexed field's value plus a pointer to the document's location on disk — so the database reads the small, sorted tree first, then jumps straight to matching documents instead of touching every document.
- Because the tree is sorted, it naturally satisfies equality (jump straight to the value), range (walk the tree between two bounds), and sort (read the tree in order — no separate in-memory sort needed) — but only when the query lines up with how the tree is built.
- A compound index is one B-tree built on concatenated field values, sorted by the first field, then the second within each first-field value, and so on. This is exactly why field order in a compound index changes what it can efficiently support.
Types of Indexes
| Type | Use case | Example |
|---|---|---|
| Single field | Query on one field | db.users.createIndex({ age: 1 }) |
| Compound | Query on multiple fields together | db.orders.createIndex({ status: 1, createdAt: -1 }) |
| Multikey | Indexing array fields | db.users.createIndex({ skills: 1 }) |
| Text | Full-text search | db.articles.createIndex({ body: "text" }) |
| Wildcard | Unknown/dynamic field names | db.logs.createIndex({ "$**": 1 }) |
| Unique | Enforce uniqueness | db.users.createIndex({ email: 1 }, { unique: true }) |
| TTL | Auto-expire documents | db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 }) |
The ESR Rule (Equality, Sort, Range) — and Why It Works
For compound indexes, order fields as: fields used for Equality matches first, then fields used to Sort, then fields used for Range filters. This is one of the most heavily tested performance concepts on the exam — and it falls directly out of how the B-tree is built:
- Equality first: an equality match (
status: "shipped") lets the engine jump straight to one narrow slice of the tree — everything after that field is now sorted within that slice, essentially "for free." - Sort second: because the tree is already sorted by the next field within that slice, MongoDB can read entries in that order directly off the index — no separate, memory-hungry in-memory sort stage required.
- Range last: a range condition (
price: {$gt:10,$lt:100}) has to scan a contiguous span of the tree rather than jump to one point. Doing this last means the equality and sort benefits above still apply before the scan narrows further. Put a range field earlier and it breaks the sorted order the later fields depend on, forcing extra work.
// Query: status == "shipped", sort by createdAt, price between 10 and 100
db.orders.find({ status: "shipped", price: { $gt: 10, $lt: 100 } }).sort({ createdAt: -1 })
// Correct index order (Equality, Sort, Range):
db.orders.createIndex({ status: 1, createdAt: -1, price: 1 })
// A "wrong order" index still works, but is slower — e.g. range before sort:
db.orders.createIndex({ status: 1, price: 1, createdAt: -1 })
// forces MongoDB to sort createdAt in memory instead of reading it off the index
Indexing Arrays (Multikey Indexes)
When you index a field that holds an array, MongoDB automatically creates a multikey index — it adds one B-tree entry per array element, all pointing back to the same document.
// Document
{ _id: 1, name: "Ada", skills: ["math", "programming", "writing"] }
// Index
db.users.createIndex({ skills: 1 })
// Under the hood, the B-tree gets 3 entries, one per element:
// "math" -> doc 1
// "programming" -> doc 1
// "writing" -> doc 1
db.users.find({ skills: "programming" }) // uses the index, IXSCAN
db.users.createIndex({ skills: 1, hobbies: 1 }) throws
an error if both skills and hobbies are arrays — MongoDB would otherwise need one
tree entry per combination of elements across both arrays, which it disallows.Other array-index details worth knowing:
- Indexing an array of embedded documents (e.g.
grades: [{subject, score}]) creates multikey entries over the whole subdocument, or over specific subfields if you index"grades.score"directly. $sizequeries cannot use a multikey index (there's no single "array length" value stored per entry) — they always trigger a collection scan unless combined with other indexed conditions.- Multikey indexes support equality, range, and
$elemMatchqueries on the array efficiently, but each additional array element indexed adds index storage and slows writes to that document.
Explain Plans
db.orders.find({ status: "shipped" }).explain("executionStats")
// Look for: IXSCAN (index used) vs COLLSCAN (full scan, usually bad)
// executionStats.totalKeysExamined vs totalDocsExamined — big gaps between
// them (docs >> keys) are a sign the index isn't selective enough
5. Data Modeling & Schema Design
This is the heart of the MongoDB exam and the part people most often underestimate. MongoDB's flexible schema means the "right" model depends entirely on your application's access patterns — not on normalization rules from relational databases. Design around how data is read and written, not just how it's structured.
Embedding vs. Referencing
Embedding nests related data inside a single document. Best when data is read together and doesn't grow unbounded.
// Embedded one-to-few (a user with a few addresses)
{
_id: 1,
name: "Ada",
addresses: [
{ city: "London", zip: "E1 6AN" },
{ city: "Paris", zip: "75001" }
]
}
Referencing stores an _id pointer to a document in another collection. Best
for one-to-many/many-to-many, large or independently-growing data, or data reused across many parents.
// Referenced one-to-many (a user with many orders)
// users collection
{ _id: 1, name: "Ada" }
// orders collection
{ _id: 101, userId: 1, total: 49.99 }
{ _id: 102, userId: 1, total: 12.50 }
| Use Embedding when... | Use Referencing when... |
|---|---|
| Data is always accessed together | Data is accessed independently |
| "Contains" / one-to-few relationship | One-to-many or many-to-many at scale |
| Child data doesn't grow unbounded | Child data grows continuously (e.g. logs) |
| You want atomic single-document writes | Related data needs independent updates |
Beyond the basic embed/reference decision, MongoDB documents a set of named schema design patterns for recurring modeling problems. The exam expects you to recognize each pattern from a scenario description. Below is each pattern with a concrete example.
Attribute Pattern
Move the variable fields into a key/value array. This lets you index the array once instead of indexing every possible field name.
// Before: hard to index every possible spec, many are null per product
{ name: "Camera X100", resolution: "24MP", waterproof: true, batteryLife: null }
// After: Attribute Pattern
{
name: "Camera X100",
specs: [
{ k: "resolution", v: "24MP" },
{ k: "waterproof", v: true }
]
}
// One index covers all specs:
db.products.createIndex({ "specs.k": 1, "specs.v": 1 })
Bucket Pattern (Group Data)
Group readings into "buckets" covering a fixed time window, with the individual readings as an embedded array plus precomputed summary fields.
{
sensorId: "sensor-42",
hour: ISODate("2026-07-26T09:00:00Z"),
readings: [
{ minute: 0, temp: 21.4 },
{ minute: 1, temp: 21.5 }
// ... up to 60 readings
],
count: 60,
avgTemp: 21.6,
minTemp: 20.9,
maxTemp: 22.3
}
Modern MongoDB also offers native timeseries collections that implement this pattern
automatically — worth knowing the built-in feature exists alongside the manual pattern.
Outlier Pattern
Embed as normal for the common case, but add a flag once a document becomes an outlier, and overflow the extra data into a separate collection.
// Typical book: reviews embedded directly
{ _id: 1, title: "Normal Book", reviews: [ /* a handful of reviews */ ] }
// Bestseller: flagged, extra reviews live elsewhere
{
_id: 2,
title: "Bestseller",
reviews: [ /* first ~100 reviews */ ],
hasExtraReviews: true
}
// book_reviews_overflow collection
{ bookId: 2, review: "..." }
Computed Pattern
Compute the value once — on write, or on a schedule — and store the result on the document so reads are a simple field lookup.
// Instead of counting views on every page load:
db.movies.updateOne(
{ _id: movieId },
{ $inc: { viewCount: 1 } }
)
// Reads just do:
db.movies.findOne({ _id: movieId }, { viewCount: 1 })
Subset Pattern
Embed only a small, frequently-accessed subset, and keep the full set in a separate collection queried on demand.
// products collection: only the 10 most helpful reviews embedded
{
_id: 1,
name: "Wireless Mouse",
topReviews: [ /* 10 most recent/helpful reviews */ ],
totalReviews: 4381
}
// reviews collection: full history, paginated on demand
{ productId: 1, rating: 5, text: "..." }
Extended Reference Pattern
Duplicate only the handful of fields you display often directly into the referencing document, alongside the reference id — avoiding a join for common reads while keeping the source of truth elsewhere.
{
_id: 501,
customerId: 77, // reference to the full customer doc
customer: { // extended reference: just what the order needs
name: "Ada Lovelace",
shippingCity: "London"
},
items: [ { sku: "A1", qty: 2 } ],
total: 59.98
}
Single Collection Pattern
Combine the polymorphic and extended reference ideas: store all related entity types in one collection
with a type discriminator plus reference fields, so one query can retrieve a mixed result set
sorted or filtered together.
{ _id: 1, type: "post", authorId: 9, text: "Hello world", createdAt: ISODate() }
{ _id: 2, type: "comment", postId: 1, authorId: 12, text: "Nice!", createdAt: ISODate() }
{ _id: 3, type: "like", postId: 1, authorId: 4, createdAt: ISODate() }
// Single query returns everything related to a post, sorted by time
db.feed.find({ $or: [{ _id: 1 }, { postId: 1 }] }).sort({ createdAt: 1 })
Archive Pattern
Periodically move old/cold documents to a separate "archive" collection (often on cheaper storage or a different cluster tier), keeping the primary collection lean and fast for current data.
// Move orders older than 2 years to an archive collection
const cutoff = new Date(); cutoff.setFullYear(cutoff.getFullYear() - 2);
const old = db.orders.find({ createdAt: { $lt: cutoff } }).toArray();
db.orders_archive.insertMany(old);
db.orders.deleteMany({ createdAt: { $lt: cutoff } });
Modeling Relationships — Quick Reference
| Relationship | Typical approach |
|---|---|
| One-to-One | Embed (or reference if the child is large/optional) |
| One-to-Few | Embed as an array |
| One-to-Many | Reference (child references parent's _id) |
| One-to-Squillions | Parent reference on the child; never embed unboundedly |
| Many-to-Many | Reference both ways, or an array of ids on one side |
Schema Design Anti-Patterns to Recognize
- Massive arrays — arrays that grow without bound (risking the 16MB limit and slow updates).
- Massive number of collections — thousands of collections (e.g. one per customer) adds overhead and hits storage engine limits.
- Unnecessary indexes — every index has a write-performance and storage cost; unused indexes should be removed.
- Bloated documents — storing rarely-used data inline instead of applying the Subset or Outlier pattern.
- Separating data accessed together — over-normalizing to the point every read needs
multiple
$lookups. - Case-insensitive queries without a matching index — causes collection scans.
6. Aggregation Framework
The aggregation pipeline processes documents through a sequence of stages, each transforming the data for the next. Think of it as a data-processing pipe.
Common Stages
| Stage | Purpose |
|---|---|
$match |
Filter documents (like find()) |
$group |
Group by a key and compute aggregates |
$project |
Reshape documents, include/exclude/compute fields |
$sort |
Order results |
$limit / $skip |
Pagination |
$lookup |
Left outer join to another collection |
$unwind |
Flatten an array into one document per element |
$addFields |
Add or overwrite fields without removing others |
$count |
Count documents at that pipeline stage |
$set / $unset |
Alias for $addFields / remove fields |
$bucket / $bucketAuto |
Group documents into ranges ("buckets") |
$facet |
Run multiple sub-pipelines in parallel, in one pass |
$replaceRoot |
Promote an embedded document to become the top-level document |
Example — revenue per customer
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customerId", totalSpent: { $sum: "$total" }, orders: { $sum: 1 } } },
{ $sort: { totalSpent: -1 } },
{ $limit: 10 }
])
Example — $lookup join
db.orders.aggregate([
{
$lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customer"
}
},
{ $unwind: "$customer" }
])
$unwind — flattening arrays
$unwind takes a document with an array field and outputs one document per array
element, copying every other field. It's what makes it possible to $group,
$match, or $sort on individual array elements instead of the whole array.
// Before $unwind — one order document, 3 items
{ _id: 1, customerId: 7, items: [
{ sku: "A1", qty: 2, price: 10 },
{ sku: "B2", qty: 1, price: 25 },
{ sku: "C3", qty: 4, price: 5 }
]}
db.orders.aggregate([
{ $unwind: "$items" }
])
// After $unwind — three separate documents, one per item
{ _id: 1, customerId: 7, items: { sku: "A1", qty: 2, price: 10 } }
{ _id: 1, customerId: 7, items: { sku: "B2", qty: 1, price: 25 } }
{ _id: 1, customerId: 7, items: { sku: "C3", qty: 4, price: 5 } }
// Common pairing: unwind, then group to get per-SKU totals across all orders
db.orders.aggregate([
{ $unwind: "$items" },
{ $group: { _id: "$items.sku", totalQty: { $sum: "$items.qty" } } }
])
$unwind drops any document whose
array field is missing, null, or an empty array. Pass
{ path: "$items", preserveNullAndEmptyArrays: true } instead of the plain string form to keep
those documents (with items set to null).$addFields / $set — computing new fields
$addFields (and its alias $set) adds new fields or overwrites existing ones,
while passing every other field through unchanged — unlike $project, you don't have to re-list
fields you want to keep.
db.orders.aggregate([
{
$addFields: {
lineTotal: { $multiply: ["$qty", "$price"] },
isBulkOrder: { $gte: ["$qty", 100] }
}
}
])
// Every original field (customerId, items, ...) is still present,
// plus the two new computed fields.
// $unset removes fields you no longer need after computing them
db.orders.aggregate([
{ $addFields: { lineTotal: { $multiply: ["$qty", "$price"] } } },
{ $unset: ["internalNotes", "tempFlag"] }
])
$bucket — grouping into ranges
db.orders.aggregate([
{
$bucket: {
groupBy: "$total",
boundaries: [0, 50, 100, 500, 1000],
default: "1000+",
output: { count: { $sum: 1 }, orders: { $push: "$_id" } }
}
}
])
// Produces one document per range: 0-50, 50-100, 100-500, 500-1000, "1000+"
$facet — multiple pipelines in one query
db.orders.aggregate([
{
$facet: {
byStatus: [{ $group: { _id: "$status", count: { $sum: 1 } } }],
topSpenders: [
{ $group: { _id: "$customerId", total: { $sum: "$total" } } },
{ $sort: { total: -1 } },
{ $limit: 5 }
]
}
}
])
// Returns one document with two arrays: "byStatus" and "topSpenders"
// — useful for building a dashboard in a single round trip
$match as early as possible to shrink the working set
before expensive stages like $lookup, $unwind, or $group.7. Transactions
MongoDB supports multi-document ACID transactions across replica sets and sharded clusters. Most schema designs (thanks to embedding) avoid needing them, but they matter when multiple documents/collections must change atomically.
const session = client.startSession();
session.startTransaction();
try {
await accounts.updateOne({ _id: "A" }, { $inc: { balance: -100 } }, { session });
await accounts.updateOne({ _id: "B" }, { $inc: { balance: 100 } }, { session });
await session.commitTransaction();
} catch (e) {
await session.abortTransaction();
throw e;
} finally {
session.endSession();
}
8. Replication
A replica set is a group of mongod processes maintaining the same data set, providing redundancy and high availability.
- Primary — receives all writes.
- Secondaries — replicate the primary's oplog; can serve reads if configured.
- Election — if the primary becomes unavailable, an eligible secondary is elected automatically.
- Write Concern — how many replica set members must acknowledge a write (e.g.
{ w: "majority" }). - Read Preference — controls whether reads go to the primary, secondaries, or nearest
node (e.g.
primary,secondaryPreferred,nearest).
Minimum recommended replica set size is 3 members (odd number, to avoid tied elections).
10. Security
- Authentication — verifying identity (SCRAM, x.509 certs, LDAP, Kerberos on Enterprise).
- Authorization (RBAC) — role-based access control; built-in roles like
read,readWrite,dbAdmin,clusterAdmin. - Encryption in transit — TLS/SSL between clients and the cluster.
- Encryption at rest — the WiredTiger storage engine can encrypt data files.
- Field-level encryption — client-side encryption for specific sensitive fields, so even MongoDB never sees plaintext.
- Auditing — logging of access and administrative actions (Enterprise/Atlas).
// Create a user scoped to one database with a built-in role
db.createUser({
user: "appUser",
pwd: "securePassword",
roles: [{ role: "readWrite", db: "shopDB" }]
})
11. Drivers & Atlas
MongoDB provides official drivers for most languages (Node.js, Python, Java, C#, Go, etc.), all implementing the same core CRUD/aggregation API against the wire protocol.
- Connection string —
mongodb+srv://user:pass@cluster.mongodb.net/dbnamefor Atlas SRV connections. - Atlas — MongoDB's fully managed cloud database service; the exam expects familiarity with basic Atlas concepts (clusters, network access lists, database users).
- Compass — the official GUI for exploring data, building queries, and analyzing schema/index usage visually.
- mongosh — the modern MongoDB shell (replaces the legacy
mongoshell).
12. Study Tips
What actually helped
- Do the free MongoDB University learning path end-to-end, including the labs — not just videos.
- Practice reading
explain()output until COLLSCAN vs IXSCAN is instant to spot. - Memorize each schema design pattern by its problem, not just its name — the exam describes scenarios, not pattern names.
- Get comfortable with aggregation stage order and what each stage's output looks like.
- Know the difference between
updateoperators ($set,$inc,$push,$addToSet,$pull) cold.
Common traps
- Confusing
updateOnevsreplaceOnebehavior. - Picking a shard key without thinking about write distribution.
- Assuming normalization (SQL instincts) is always "correct" — MongoDB rewards designing for access patterns.
- Forgetting the 16MB document size limit when justifying embedding choices.
- Mixing up read preference modes and when each is safe to use.
13. Resources
- MongoDB University — free official courses and the Associate Developer learning path.
- MongoDB Manual: Schema Design Patterns — official reference for the patterns in this guide.
- MongoDB Manual: Schema Design Anti-Patterns
- MongoDB Manual — full reference documentation.
- Aggregation Pipeline Reference
Good luck — the exam rewards understanding why a pattern fits a scenario, not memorizing syntax. Work through real examples like the ones above and you'll recognize them instantly on test day.