Dictionary serialization for DynamoDB ExclusiveStartKey not working
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When ExclusiveStartKey fails in DynamoDB, the bug is usually not in pagination logic itself. The usual cause is a mismatch between the key structure you pass back and the API layer you are calling, because the boto3 resource API and low-level client API expect different key formats.
What ExclusiveStartKey Is Supposed to Be
DynamoDB pagination works like this:
- you run
QueryorScan - DynamoDB returns
LastEvaluatedKeyif another page exists - you pass that exact value back as
ExclusiveStartKey
The safest implementation rule is simple: do not reconstruct the key by hand unless you truly have to. Reuse the value DynamoDB already returned.
Resource API Versus Client API
This is the source of most bugs.
With the high-level boto3 resource API, keys are ordinary Python values.
If you use table.query, you pass that key back in the same resource-style shape.
The low-level client API is different. It expects DynamoDB's typed JSON format.
If you use client.query, the ExclusiveStartKey must stay in that typed format.
Do Not Cross the Formats
This is the classic failure pattern:
- use
table.query - build an
ExclusiveStartKeylike{"user_id": {"S": "u-100"}} - get a validation error
Or the reverse:
- use
client.query - pass plain Python values such as
{"user_id": "u-100"} - get another validation error
The fix is to keep each key in the format required by the API layer that produced it.
Composite Keys Must Be Complete
If the table uses both a partition key and a sort key, ExclusiveStartKey must include both. Partial keys are not enough for pagination.
For example, if the table key schema is:
- partition key:
user_id - sort key:
created_at
then a valid start key must contain both values.
That is why passing LastEvaluatedKey through directly is so reliable. DynamoDB already knows the exact key shape that belongs to the last evaluated item.
Serialization Problems in Python
Another source of trouble is serializing the key to JSON for storage or transport. DynamoDB numeric values often interact with Python Decimal objects, and naive JSON conversion can change types.
For example, a sort key that should stay numeric can accidentally become a string during a custom JSON round trip. When that corrupted key comes back as ExclusiveStartKey, pagination breaks.
If you need to persist the token outside the immediate request flow, preserve the exact shape carefully and restore it with the same type semantics.
Use DynamoDB Serializers Only When Necessary
If you genuinely need to translate plain Python dictionaries into low-level DynamoDB typed format, use boto3's serializers instead of hand-writing the conversion.
This is much safer than manually building {"S": ...} and {"N": ...} structures everywhere.
Common Pitfalls
Mixing resource-style keys and client-style typed keys is the biggest source of this problem.
Rebuilding ExclusiveStartKey manually instead of reusing LastEvaluatedKey also creates unnecessary failure points.
Forgetting the sort key on a composite-key table is another common issue.
Finally, careless JSON serialization can corrupt number types and make a previously valid pagination token unusable.
Summary
- '
ExclusiveStartKeymust match the exact key format expected by the API you are calling' - reuse
LastEvaluatedKeydirectly whenever possible - resource API uses plain Python values, while client API uses DynamoDB typed JSON
- composite-key tables require the full key, not only the partition key
- if you must serialize keys, preserve types carefully or use DynamoDB serializers

