DynamoDB: Querying, Pagination & Capacity
Scan vs Query, pagination with LastEvaluatedKey, Streams, capacity modes, and TTL, all in one place.
This page covers reading data back out of DynamoDB at scale: which read operation to use, how to page through large results, and the built-in features that run in the background. See DynamoDB: Reading & Writing in Code for the SDK and writes first.
Scan vs Query
Both return more than one item; they get there completely differently.
- Query fetches by a known partition key (optionally narrowed further by a sort key condition). It only ever reads the items that actually match that key, cheap and fast regardless of table size.
- Scan reads every item in the table (or index), then applies whatever filter comes after, cheap to write, expensive to run, and the cost scales with the table's total size, not the result size.
A table with only a partition key, no sort key, can't use Query to get "all items." The partition key has to be unique per item by definition. A Query against it can only ever return the single matching item. Getting every item out of a hash-only table means Scan is the only option. There's no key to query across.
Pagination: LastEvaluatedKey and ExclusiveStartKey
Both Scan and Query cap a single response at 1MB of data. A table with more data than that behind one request doesn't error. It returns a partial result plus a LastEvaluatedKey, DynamoDB's way of saying "there's more, resume from here."
Getting everything means looping: check for a LastEvaluatedKey in the response, and if present, feed it back in as the next request's ExclusiveStartKey before issuing that request. Repeat until a response comes back with no LastEvaluatedKey at all, that's the signal there's nothing left.
let lastEvaluatedKey;
let allItems = [];
do {
if (lastEvaluatedKey) params.ExclusiveStartKey = lastEvaluatedKey;
const data = await docClient.send(new ScanCommand(params));
allItems = allItems.concat(data.Items);
lastEvaluatedKey = data.LastEvaluatedKey;
} while (lastEvaluatedKey);The same Limit parameter that caps items per page (rather than waiting for the 1MB ceiling) triggers the exact same LastEvaluatedKey behavior, useful for testing this loop without needing megabytes of real data to trigger it naturally.
Streams
Enabling Streams means every write, insert, update, or delete, gets emitted as an event. Configure what each event carries: key attributes only, the full new item, the full old item, or both. A Lambda function can listen directly, which is what makes DynamoDB a natural event-driven trigger, not just a place data ends up.
Capacity modes
- On-demand: no capacity planning, pay per request. Absorbs a large volume of writes without pre-provisioning, the right default when traffic is spiky or unpredictable.
- Provisioned: set read/write capacity ahead of time, cheaper at steady, predictable volume.
TTL (time to live)
An attribute that tells DynamoDB when to delete an item automatically, no scheduled cleanup job required. Used for a circuit breaker heartbeat (a status: down record with a short TTL, checked before calling an external system) and for idempotency table entries.
Other built-in features
- Encryption at rest, on by default.
- Point-in-time recovery, restore to any point in the retention window.
- Global Tables, multi-region replication, for disaster recovery and multi-region architectures.
The Essentials
- Query is cheap and scoped to a key; Scan reads everything. A hash-only table needs Scan to return "all items."
- Both Scan and Query cap out at 1MB per response. Loop on
LastEvaluatedKey/ExclusiveStartKeyto get everything. - TTL deletes items automatically, useful for heartbeat and idempotency tables alike.
Where this showed up
- Check the fuse before you try the call: the heartbeat table and TTL.
Further Reading and Watching
Keep reading