Ray gets the offset-versus-cursor tradeoff right and the mechanism wrong: PostgreSQL's docs say skipped rows "still have to be computed inside the server," and the query on screen is missing the ORDER BY that makes OFFSET unsafe at any offset value
Watch on TikTok
A 101.733333 s clip in a QuickTime/MOV container (mov,mp4,m4a,3gp,3g2,mj2, .mp4 extension), 1080x1920 portrait at a 0.5625 aspect, HEVC Main profile video tagged hvc1 in yuv420p at a constant 30/1 fps across 3052 frames and 162,048 bps, paired with HE-AACv2 audio at 44,100 Hz stereo and 64,121 bps across 2194 frames, for an overall 232,859 bps and a file of exactly 2,961,201 bytes, which matches the filesize field in metadata.json byte for byte; the TikTok format id is bytevc1_1080p_232858-1, and the separately extracted audio.mp3 runs 101.773061 s at 1,078,648 bytes and 84,788 bps, so the audio track outlives the video track by 0.04 s. The timestamp field is epoch 1791346045, which is 2026-10-07T04:07:25Z, consistent with the upload_date of 20261007. The uploader is therealraymondjones (uploader_id 7322867085993378822) with the display nickname "Ray"; audio is credited as track "original sound" by artist "Ray", so there is no licensed music. The description is 205 characters and carries five hashtags: #SystemDesign, #APIDesign, #TechExplained, #Programming, #Database. At capture time (the epoch field is 1792407676, which is 2026-10-19T11:01:16Z, and metadata.json has an mtime of 2026-10-19 06:01:17 local) the counts were 27,000 views, 639 likes, 5 comments, 11 reposts, and 259 saves. I read 12 of the 51 extracted frames (001, 005, 010, 014, 019, 024, 028, 033, 040, 046, 050, 051) sampled at 2 s intervals, against 17 transcript segments totalling 314 words. Frame 001 at 0 s is a title card reading "Pagination vs. Cursors: Which is Faster?" in a white rounded panel over a dark Excalidraw-style canvas already holding a handwritten-font JSON response: "results": [ then "observation_id": "obs_829381", "plate": "ABC123", "confidence": 0.98, "captured_at": "...", "camera_id": "cam_4829", "location": { "lat": 37.81, "lon": -122.27 }, "vehicle": { "color": ..., "make": ... }, closing with "next_cursor"; a webcam inset in the lower right shows a man in white over-ear headphones and wire-frame glasses in a cream polo against a bookshelf, with the burned-in caption "LIKE". Frame 005 at roughly 8 s captions "PAGINATION" and catches a canvas zoom mid-transition, with a blue handwritten "API" heading, the fragment "1. Offset Paginat..." overlapping POST /search/cameras, and a request body of { "plate": "ABC123", "start_time": "2026-10-05T00:00:00Z", "end_time": "2026-10-06T00:00:00Z", "case_number": "CASE-48392", "reason": "Stolen vehicle investigation" }. Frame 010 at roughly 18 s ("SO WHEN WE") settles on that same request. Frame 014 at roughly 26 s ("THIS IS HOW IT") shows him typing SELECT * FROM DB with a text cursor; frame 019 at roughly 36 s ("WE'LL PUT") has rewritten it as SELECT * FROM TABLE / WHERE |; frame 024 at roughly 46 s ("TO 50") adds WHERE <CONDITION> / LIMIT 50|; frame 028 at roughly 54 s ("ALLOWS US") adds OFFSET 100 and shows the four-line block selected inside a bounding box with a move handle; frames 033 and 040 at roughly 64 s and 78 s ("RECORDS") show that same four-line query being dragged around the canvas. Frames 046, 050 and 051 at roughly 90 s, 98 s and 100 s ("CHUNK TO GET", "USE THAT", "WHEN WE DO") pan to put the /search/cameras request on the left and the response on the right with "next_cursor": "..." boxed by a selection rectangle, and reveal a second endpoint below, /hotlists/{hotlist_id}/plates. The spoken argument is that a search endpoint over a large table needs either LIMIT/OFFSET or a cursor, that the offset approach "does kind of like a full scan," and that a cursor returned with each page tells the client which chunk to request next. No frame I read contains an ORDER BY, an index definition, an EXPLAIN plan, a terminal, a timing measurement, or a request that actually sends the cursor back, so the title card's "Which is Faster?" is never measured anywhere in the clip. On the transcript-end check: the last substantive segment ends at 101.66 s, within the 101.773061 s of audio, but segment 17 is the single filler word "you" timestamped 101.66 s to 102.92 s, which runs 1.15 s past the end of the audio track and 1.19 s past the end of the video stream; that is a Whisper hallucination past the end of audio and I treat it as invented.
Correction: "it does kind of like a full scan" describes a mechanism PostgreSQL does not use, and the clause order is backwards
At 57.14 s the video says "limit it and then offset it and it does kind of like a full scan actually um so it grits all the data from the condition and then it applies like this offset to get the next one," and at 50.02 s it sets that up with "this allows us to do is retrieve all the records limit it and then offset it." (The transcript's "grits" is Whisper mangling "gets.") The description under the video repeats the framing: "Avoid full table scans and overwhelming your system!"
The PostgreSQL documentation states the real cost in one sentence: "The rows skipped by an OFFSET clause still have to be computed inside the server; therefore a large OFFSET might be inefficient." Computing and discarding 100 rows is a different operation from scanning a table. If an index covers the WHERE predicate and the sort key, the planner can walk the index and touch roughly OFFSET + LIMIT rows, which in his own example is 150 rows, regardless of whether the table holds a thousand observations or a billion. Nothing in the documented behaviour requires reading every matching row.
The order of operations is also reversed. The PostgreSQL SELECT reference specifies that start "specifies the number of rows to skip before starting to return rows," and that "when both LIMIT and OFFSET are specified, start rows are skipped before starting to count the count rows to be returned." The engine skips first and counts second. It never materialises the full result and then slices it, which is what "retrieve all the records, limit it and then offset it" describes.
Credit where the intuition lands: the cost model he is reaching for is correct in shape. Offset cost scales with the offset value rather than with the page size, which is why page 1 is cheap and page 10,000 is not. And there is one documented case close to his description. The MySQL manual notes that "if a filesort must be done, all rows that match the query without the LIMIT clause are selected, and most or all of them are sorted, before the first row_count are found." When no index supports the ordering, the engine really does produce the whole matching set before the limit applies. That is a property of the missing index, not of OFFSET itself.
Omission: no frame shows an ORDER BY, and PostgreSQL documents that as the thing that breaks paginated LIMIT/OFFSET
Frames 024, 028, 033 and 040, spanning roughly 46 s to 78 s, show the query in its final form across four lines: SELECT * FROM TABLE, WHERE <CONDITION>, LIMIT 50, OFFSET 100. There is no ORDER BY in any of them. The word "order" does not appear in the 314 transcribed words, and neither does "sort" or "index."
PostgreSQL's own warning is blunt: "When using LIMIT, it is important to use an ORDER BY clause that constrains the result rows into a unique order. Otherwise you will get an unpredictable subset of the query's rows. You might be asking for the tenth through twentieth rows, but tenth through twentieth in what ordering? The ordering is unknown, unless you specified ORDER BY." It goes further on exactly the pagination case: "The query optimizer takes LIMIT into account when generating query plans, so you are very likely to get different plans (yielding different row orders) depending on what you give for LIMIT and OFFSET. Thus, using different LIMIT/OFFSET values to select different subsets of a query result will give inconsistent results unless you enforce a predictable result ordering with ORDER BY."
Two consequences follow for the query on screen. Page 2 and page 3 of this endpoint can overlap or omit rows even with a completely static table, because the planner is free to choose a different plan for OFFSET 100 than it chose for OFFSET 0. And the word "unique" in the documentation is load bearing. An ALPR feed keyed on captured_at will produce ties when two cameras record in the same millisecond, so ordering on captured_at alone still leaves the tied rows free to swap between requests. The ordering has to end in a unique tiebreaker, for instance ORDER BY captured_at, observation_id.
This matters more than the full-scan point. The video's criticism of offset pagination is a performance criticism, and performance is the weaker of the two arguments at OFFSET 100. The correctness problem is present at every offset value, including the one he drew.
Clarification: the strongest case against offset pagination is concurrent writes, and the video never names it
At 77.04 s the video justifies the cursor entirely on volume: "a lot of records like a lot a lot of records and we don't want to return all of them all at the same time we don't want to even want the code to be doing that so what we can do is have a cursor." Volume is already handled by LIMIT 50. The argument that distinguishes a cursor from an offset is what happens between two requests.
This follows directly from the documented semantics rather than from any claim the video makes. OFFSET 100 means "skip 100 rows of the current result set." If three new observations land ahead of the client's position between the request for rows 51 to 100 and the request for rows 101 to 150, every row shifts down by three positions, and the second response re-delivers three rows the client already has. Deletions produce the mirror image and the client silently never sees three rows. His worked example is the worst possible case for this, because an automated license plate reader feed is an append-heavy stream sorted by capture time, and a stolen-vehicle search such as the CASE-48392 request in frame 010 is exactly the query a user would page through while new plates are still arriving.
Cursors narrow that failure window rather than closing it. Intercom's API documentation states the limit of its own cursor implementation: "The starting_after context is stateless. Consequently, if items are updated between 2 paginated queries, this can lead to duplicate or missed records." A cursor is stable with respect to the sort key it encodes. Rows that change that key between requests move across the cursor boundary and are either repeated or lost. Which returns the point to the previous section: a cursor is only as deterministic as the ORDER BY it is built on, and no ORDER BY appears anywhere in this video.
Unanswered: the title card asks "Which is Faster?" and nothing in 101.7 seconds measures anything
Frame 001 at 0 s puts "Pagination vs. Cursors: Which is Faster?" in a white panel across the top third of the screen. Across the 51 frames extracted at 2 s intervals there is no EXPLAIN output, no query plan, no timer, no row count, and no terminal. The word "faster" does not appear in the transcript at all. There is no benchmark figure in the video, so there is no number to trace to a source.
The documentation supports a narrower claim than the title card implies. PostgreSQL says "a large OFFSET might be inefficient," with the conditional doing real work. The example drawn on screen is OFFSET 100, which is 100 rows skipped. On an indexed query that is two extra index pages and is not measurably slower than a keyset seek. The performance gap opens at deep offsets, where the engine has to produce and discard tens or hundreds of thousands of rows to reach the page boundary, and it closes entirely on page one, where OFFSET 0 and a cursor-less first request are the same query.
The cost the video does not mention runs the other way. A cursor cannot jump to an arbitrary page. Stripe's list API exposes limit, starting_after and ending_before and a has_more boolean, with no offset or page parameter of any kind, so a client cannot ask for page 40 without walking pages 1 through 39. Offset pagination buys random access and a total page count. That is the actual tradeoff behind "which is faster," and it is the reason both styles still ship.
Verified: the cursor contract he describes is the one Slack and the Relay connections spec implement, and next_cursor on screen is Slack's exact field name
At 86.08 s the video describes the mechanism: "we can have a cursor which would just tell us like the next chunk to get the next chunk that we're missing so we can get our first chunk it'll return us like here's the next thing that you need and we can pass that and use that um when we do our next request." That is an accurate description of cursor pagination as the major APIs define it, and the response he drew uses the standard field name.
Slack's Web API pagination documentation describes cursors in nearly the same words: "Cursors are like pointers. Pointers point at things: they reference a specific iota, a place in the list where your last request left off." It specifies that paginated responses include a top-level response_metadata object carrying "a next_cursor when there are additional results to be retrieved," and that an empty, null or missing next_cursor means the list is exhausted. The JSON visible in frames 001, 046, 050 and 051 ends with "next_cursor" at the same nesting level as "results", which is the same shape minus Slack's response_metadata wrapper.
The GraphQL Cursor Connections Specification adds the constraint the video leaves out. Cursors are opaque: "The result of this field should be considered opaque by the client, but will be passed back to the server." The spec defines PageInfo with hasNextPage, hasPreviousPage, startCursor and endCursor, and the arguments first, after, last and before for forward and backward traversal. The video shows only the forward half, and no frame I read shows a request that actually sends the cursor back, so the loop he describes verbally is never drawn.
Stripe implements the same idea without opacity: starting_after and ending_before "accept an existing object ID value," and the two are mutually exclusive. That is the simplest possible cursor, a primary key, and it works precisely because Stripe lists return "objects in reverse chronological order," which is a fixed, documented sort.
Verified with one quibble: "I believe we can offset" is correct and portable, "SELECT * FROM DB" is not
At 36.94 s the video hedges on the syntax: "we can limit it to 50 and i believe we can offset." The hedge is unnecessary. PostgreSQL's SELECT reference documents LIMIT { count | ALL } followed by OFFSET start, which is exactly what frame 028 shows at roughly 54 s. MySQL documents the same spelling and says so explicitly, supporting LIMIT row_count OFFSET offset "for compatibility with PostgreSQL" alongside its own older LIMIT offset, row_count form, where SELECT * FROM tbl LIMIT 5,10 retrieves rows 6 through 15. The SQL:2008 standard spelling is OFFSET start { ROW | ROWS } FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } { ONLY | WITH TIES }, which PostgreSQL also accepts. The clause he doubted is the portable one.
The clause worth doubting is the one he wrote first. At 22.6 s he says "select everything from db right this is how it looks inside of the the actual code base," and frame 014 at roughly 26 s shows him typing SELECT * FROM DB literally. FROM takes a table, view or subquery, never a database name. He fixes it on the canvas within ten seconds: frame 019 at roughly 36 s reads SELECT * FROM TABLE / WHERE |. The spoken line at 30.32 s, "select everything from table where," matches the corrected version, so the error is confined to the first pass on the whiteboard.
One construct the video would have needed to close its own argument is the keyset predicate that a cursor decodes into, and PostgreSQL ships it as a documented, SQL-compliant feature. Row constructor comparison evaluates (captured_at, observation_id) > ('2026-10-05T12:00:00Z', 'obs_829381') by comparing "row elements left-to-right, stopping as soon as an unequal or null pair of elements is found," and the documentation notes that the non-array forms of these comparisons "are SQL-compliant." Combined with ORDER BY captured_at, observation_id LIMIT 50 and a matching index, that turns every page into a single index seek with no skipped rows to compute. No frame in this video shows it.
Key Takeaways
- Correction: The video says at 57.14 s that
OFFSET"does kind of like a full scan" and "gets all the data from the condition and then it applies like this offset," and the description repeats "Avoid full table scans." PostgreSQL's documentation says the skipped rows "still have to be computed inside the server; therefore a largeOFFSETmight be inefficient," and theSELECTreference saysstartrows are skipped "before starting to count thecountrows to be returned." An indexed query touches roughlyOFFSET + LIMITrows and never scans the table. - Partial correction: The intuition that offset cost grows with the offset value is right, and the MySQL manual documents one case matching his description, where "if a filesort must be done, all rows that match the query without the
LIMITclause are selected, and most or all of them are sorted, before the firstrow_countare found." That is caused by a missing index, not byOFFSET. - Correction: The on-screen query in frames 024, 028, 033 and 040 is
SELECT * FROM TABLE WHERE <CONDITION> LIMIT 50 OFFSET 100with noORDER BY, and "order," "sort" and "index" appear nowhere in the 314 transcribed words. PostgreSQL states that differentLIMIT/OFFSETvalues "will give inconsistent results unless you enforce a predictable result ordering withORDER BY," and that the ordering must be unique, whichcaptured_atalone is not. - Clarification: The video justifies cursors only on response size at 77.04 s, which
LIMIT 50already solves. The argument that separates the two styles is behaviour under concurrent inserts and deletes, which follows from the documented meaning ofOFFSETas "skip N rows of the current result set." The video never raises it. - Clarification: Cursors are not an unconditional guarantee. Intercom's API docs state that "the
starting_aftercontext is stateless. Consequently, if items are updated between 2 paginated queries, this can lead to duplicate or missed records." Stability holds only for the sort key the cursor encodes. - Unverified: The title card at 0 s asks "Pagination vs. Cursors: Which is Faster?" No frame among the 51 extracted contains an
EXPLAINplan, a timing, a row count or a terminal, and "faster" never appears in the transcript. The video supplies no benchmark figure, so there is none to trace. - Context: The tradeoff the video omits is random access. Stripe's list API exposes only
limit,starting_after,ending_beforeandhas_more, with no offset or page parameter, so a cursor client cannot jump to page 40 without walking the 39 pages before it. - Verified: The cursor loop described at 86.08 s matches how Slack documents it, where
response_metadatacarries "anext_cursorwhen there are additional results to be retrieved," and the response drawn on screen uses that exact field name. The Relay connections spec adds that cursors are opaque and thatPageInfocarrieshasNextPage,hasPreviousPage,startCursorandendCursor. - Verified: The hedge at 36.94 s, "i believe we can offset," is correct. PostgreSQL documents
LIMIT { count | ALL } OFFSET start, and MySQL supportsLIMIT row_count OFFSET offset"for compatibility with PostgreSQL." - Correction:
SELECT * FROM DB, typed in frame 014 at roughly 26 s and spoken at 22.6 s, is not valid SQL, sinceFROMtakes a table, view or subquery. He corrects it on the canvas toSELECT * FROM TABLEby frame 019 at roughly 36 s. - Context: Transcript segment 17 is the single word "you" timestamped 101.66 s to 102.92 s, running 1.15 s past the 101.773061 s audio track. It is a Whisper hallucination past the end of audio and is not spoken in the video.
Resources
- LIMIT and OFFSET, PostgreSQL 18 documentation establishes the two sentences this document leans on hardest: that skipped rows "still have to be computed inside the server; therefore a large OFFSET might be inefficient," and that differing LIMIT/OFFSET values "will give inconsistent results unless you enforce a predictable result ordering with ORDER BY."
- SELECT, PostgreSQL 18 documentation establishes the
LIMIT { count | ALL } OFFSET startsyntax, the rule thatstartrows are skipped before thecountrows are counted, the requirement for a uniqueORDER BY, and the SQL:2008OFFSET ... FETCH FIRST ... ROWS ONLYalternative. - Row Constructor Comparison, PostgreSQL 18 documentation establishes that row comparisons evaluate elements "left-to-right, stopping as soon as an unequal or null pair of elements is found" and that the non-array forms are SQL-compliant, which is the keyset predicate a cursor decodes into.
- SELECT Statement, MySQL 8.4 Reference Manual establishes that MySQL supports
LIMIT row_count OFFSET offset"for compatibility with PostgreSQL" alongsideLIMIT offset, row_count, confirming the syntax the video hedges on is portable. - LIMIT Query Optimization, MySQL 8.4 Reference Manual establishes the filesort case where "all rows that match the query without the LIMIT clause are selected, and most or all of them are sorted, before the first row_count are found," the one scenario close to the video's full-scan description.
- Pagination, Slack Web API documentation establishes the
response_metadataandnext_cursorcontract, and the pointer metaphor the video reaches for, matching thenext_cursorfield drawn on screen. - GraphQL Cursor Connections Specification, Relay establishes that cursors are opaque to clients, and the
PageInfofieldshasNextPage,hasPreviousPage,startCursorandendCursorwith thefirst/after/last/beforearguments. - Pagination, Stripe API reference establishes a cursor implementation built on object IDs via
starting_afterandending_beforewith ahas_moreflag and no offset or page parameter, which is the random-access tradeoff the video omits. - Pagination, Intercom developer documentation establishes a first-party statement of the limit of cursors: "The
starting_aftercontext is stateless. Consequently, if items are updated between 2 paginated queries, this can lead to duplicate or missed records."
I could not verify the creator's biography against a primary source. raymondjones.dev returns a client-rendered React shell with no server-side content, so I did not cite it, and I identify him only by the handle and nickname recorded in metadata.json.
Published October 7, 2026. Writeup generated from a favorited TikTok.