Middle · Trace · API design

Trace the complete program for bound keyset pagination and continuation rules. State the exact stdout, then justify the state transitions and relevant edge conditions.

Think through your answer before opening the explanation.

Coding interviews

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

var rows=new[]{new Row(2,"b"),new Row(1,"a"),new Row(4,"d"),new Row(3,"c")};
var first=Page(rows,null,2); var second=Page(rows,first[^1].Id,2);
Console.WriteLine("first:"+string.Join(",",first.Select(x=>x.Id))); Console.WriteLine("next:"+string.Join(",",second.Select(x=>x.Id)));
bool rejected=false; try { Page(rows,null,4); } catch(ArgumentOutOfRangeException) { rejected=true; } Console.WriteLine("limit rejected:"+rejected.ToString().ToLowerInvariant());
static Row[] Page(IEnumerable<Row> rows,int? after,int size) { if(size is <1 or >3) throw new ArgumentOutOfRangeException(nameof(size)); return rows.Where(x=>after is null || x.Id>after.Value).OrderBy(x=>x.Id).Take(size).ToArray(); }
sealed record Row(int Id,string Name);
Reveal answer and reasoning

The output lines, in order, are “first:1,2”; “next:3,4”; “limit rejected:true”. The first request has no continuation and returns IDs one and two despite the unsorted source. Reusing two as after returns three and four. A requested size four is rejected before enumeration. No next token is fabricated when the consumer has no returned last row, and null is distinct from an actual integer boundary.

Sorting occurs after filtering, which means the continuation is interpreted against the entire current source rather than against its incidental array position. The first result is one,two and the second three,four even though the physical input begins with ID two. This distinguishes keyset semantics from Skip on an unsorted source.

For n source rows and m matching rows, filtering examines O(n) values and ordered-prefix evaluation buffers O(m) candidates. A full-sort baseline costs O(m log m), while .NET LINQ can optimize Take with partial selection; exact comparison behavior is implementation-dependent. An indexed ordered data source has another cost model. The small result page does not establish bounded total memory or an index seek.

Nonunique ordering keys can skip or repeat rows across pages unless a unique secondary key becomes part of the token. Concurrent inserts and deletes also require an explicit consistency policy. Local arrays provide no snapshot token, authorization boundary or tamper-resistant continuation encoding.

What a strong answer covers

  • Exact output: first:1,2 | next:3,4 | limit rejected:true
  • The first request has no continuation and returns IDs one and two despite the unsorted source.
  • Nonunique ordering keys can skip or repeat rows across pages unless a unique secondary key becomes part of the token.

Common pitfall

Nonunique ordering keys can skip or repeat rows across pages unless a unique secondary key becomes part of the token. Concurrent inserts and deletes also require an explicit consistency policy. Local arrays provide no snapshot token, authorization boundary or tamper-resistant continuation encoding.

Follow-up questions

  1. Insert a new row below and above the first continuation, then predict the next page. Hint: strict greater-than excludes earlier keys; that behavior is not the same as a frozen snapshot. Test the final empty page without indexing its last element.
  2. Which data must a continuation token bind when rows belong to different tenants?

Self-review

Choose what to revisit. These marks are your own assessment and are separate from lesson completion.

Primary references

Open lesson and code ↗

← All interview questions