Working with Cursors

1. Understanding Cursor Behavior

AspectDetail
LazyServer batches results, client pulls as needed
Default batch101 docs first batch, 16MB subsequent
Idle timeout10 minutes (server)
ResourceHolds locks/snapshots on server

2. Iterating Cursor Results

PatternDescription
for await (const d of cursor)Driver async iteration
cursor.forEach(fn)Imperative
while (cursor.hasNext())mongosh
cursor.tryNext()Non-blocking peek

3. Converting Cursor to Array

MethodWarning
toArray()Loads all results in memory
Use caseSmall bounded results only
Anti-patterntoArray() on unbounded find()

4. Counting Cursor Results

MethodNotes
countDocuments(filter)Accurate; uses aggregation
estimatedDocumentCount()Fast, from metadata; no filter
cursor.count() DEPRECATEDReplaced by countDocuments

5. Setting Cursor Limit

MethodDescription
.limit(n)Cap returned docs
Negative limitSingle batch, then close cursor

6. Skipping Cursor Results

AspectDetail
.skip(n)Skip n docs server-side
PerformanceO(n) cost; avoid large skips for pagination
Better paginationRange query on indexed sort key

Example: Range-based pagination

// Page N uses last _id of page N-1
const page = await db.posts.find({ _id: { $gt: lastId } }).sort({ _id: 1 }).limit(20).toArray();
const lastId = page.at(-1)?._id;

7. Sorting Cursor Results

FormEffect
.sort({field: 1})Ascending
.sort({field: -1})Descending
.sort({a:1, b:-1})Composite
In-memory limit32MB unless backed by index
$meta sortBy text score: .sort({score: {$meta: "textScore"}})

8. Setting Batch Size

MethodEffect
.batchSize(n)Docs per network round-trip
Trade-offSmall = lower memory, more RTT; large = opposite
CapServer limits batches to 16MB regardless

9. Using Tailable Cursors

OptionPurpose
tailableStays open at end of capped collection
awaitDataBlock briefly for new docs
Use caseLog tailing, change-stream-like

10. Using noCursorTimeout Option

AspectDetail
PurposePrevent server-side 10-minute idle close
RiskCursor leaks if client forgets to close
Use caseLong-running ETL / batch jobs

11. Closing Cursors

MethodEffect
cursor.close()Frees server resources immediately
killCursors commandServer-side close by id
Auto-closeWhen exhausted or idle timeout

12. Understanding Cursor Expiration

ConditionResult
10-min idleServer closes; CursorNotFound error
Connection dropCursor invalidated
noCursorTimeoutDisables idle expiration
RecoveryRe-issue query; cannot resume