DynamoDB Scan vs Query: Differences, Cost and When to Use Each

Written by Rafal Wilinski
Last updated: October 23rd, 2026
A Query reads only the items that share one partition key value, optionally narrowed by a condition on the sort key, so its cost and latency follow the size of the result. A Scan reads every item in a table or index and consumes read capacity for all of it, including items a filter later throws away. On a table of one million 2 KB items, finding one customer's 40 orders costs about 10 read capacity units with a Query and about 250,000 with a Scan. Use Query (or GetItem) whenever you know the partition key, and add a global secondary index when you don't.
Query vs Scan at a glance
| Query | Scan | |
|---|---|---|
| What it reads | Items with one partition key value, optionally a sort key range | Every item in the table or index |
| Required input | Equality (=) on the partition key; optional sort key condition | Only the table name |
| Read cost | Total size of the items read, rounded up to the next 4 KB | Total size of every item evaluated, rounded up to 4 KB |
| Time | Grows with the result | Grows with the table |
| Data per request | Up to 1 MB, read before any filter | Up to 1 MB, read before any filter |
Limit | Caps the items evaluated per request, before the filter | Same |
FilterExpression | Non-key attributes only; doesn't reduce cost | Any attribute, keys included; doesn't reduce cost |
| Result order | Sorted by sort key; ScanIndexForward: false reverses it | No order across partition keys |
| Parallelism | Send Queries for different keys concurrently | Built-in parallel scan with Segment and TotalSegments |
| Read consistency | Eventual by default; strong on tables and LSIs at twice the cost; GSIs are eventual only | Same |
| Indexes | Tables, local and global secondary indexes (IndexName) | Same |
| IAM action | dynamodb:Query | dynamodb:Scan |
Which read operation to use

Start from what you know about the items you want:
- One item and its full primary key:
GetItem. - Items that share a partition key:
Query, with a sort key condition to narrow the range. - Several items whose full primary keys you know, under different partition keys:
BatchGetItem. - Only a non-key attribute: Query a global secondary index on that attribute if the lookup is frequent, or Scan if it's a one-off.
GetItem and BatchGetItem need the table's full primary key. If you only know an index key, Query the index; there is no GetItem on an index.
How Query and Scan consume read capacity
DynamoDB meters reads in 4 KB units. A strongly consistent read of up to 4 KB costs one read capacity unit (RCU), and an eventually consistent read, the default for both operations, costs half of one. On-demand tables use the same arithmetic and call the units read request units.
Query adds up the size of every item it reads in a request and rounds the total up to the next 4 KB. Scan does the same for every item it evaluates, whether or not that item ends up in the result. Take an orders table with 1,000,000 items of about 2 KB each, where customer c-1001 has 40 orders:
| Read | Data read | Strongly consistent | Eventually consistent |
|---|---|---|---|
Query customerId = "c-1001" | 40 × 2 KB = 80 KB | 20 RCU | 10 RCU |
Scan with filter customerId = :c | 1,000,000 × 2 KB = 2,000,000 KB | 500,000 RCU | 250,000 RCU |
| GetItem for one of those orders | 2 KB, rounded up to 4 KB | 1 RCU | 0.5 RCU |
The Scan also takes about 1,950 requests, because each one stops after 1 MB.
Because the rounding applies to the total rather than to each item, small items are cheap to Query: 1,500 items of 64 bytes add up to 96 KB, or 24 RCU for a strongly consistent Query. Fetching the same items one at a time with GetItem would cost 1,500 RCU, since each read rounds up to 4 KB on its own.
Two more rules follow from the same accounting. Select: "COUNT" costs as much as returning the items, because DynamoDB still reads them. And a Query or Scan that matches nothing still consumes a small minimum. To see the real figure for any request, set ReturnConsumedCapacity to TOTAL (or INDEXES for a per-index breakdown) and read ConsumedCapacity in the response. The AWS page on read capacity consumption has the full rules.
Filters don't make a Scan cheaper
FilterExpression runs after DynamoDB has read a page and before it returns the page to you. You pay for every item read; the filter only shrinks the response. The response shows the gap: ScannedCount is the number of items evaluated and Count is the number that passed the filter. A high ScannedCount with a low Count means you are paying to read data you discard.
In a Query, the filter can't reference the partition key or the sort key. Conditions on keys belong in KeyConditionExpression, which is the only place they reduce what gets read. In a Scan, the filter can use any attribute. The FilterExpression guide covers the operators and functions.
ProjectionExpression shrinks the payload, not the bill
ProjectionExpression returns only the attributes you name. DynamoDB still reads whole items, so consumed capacity stays the same, but responses get smaller and faster to transfer and parse. On a global secondary index you can only request attributes that are projected into the index.
Both return at most 1 MB per request
Query and Scan read up to 1 MB of data per request, or up to Limit items if you set it, and then apply the filter. If there is more to read, the response includes LastEvaluatedKey. Send the same request again with that value as ExclusiveStartKey, and stop when a response has no LastEvaluatedKey. A page can contain zero items and still carry a LastEvaluatedKey when the filter rejected everything on it, so an empty page doesn't mean you're done.
The SDK paginators in the examples below run this loop for you. DynamoDB pagination covers cursors for APIs, Limit and counting.
Parallel scan
A sequential Scan reads one partition at a time, so a big table takes a long time to scan however much capacity it has. A parallel scan splits the work into segments: each worker sends Scan requests with the same TotalSegments and its own Segment, numbered from 0 to TotalSegments - 1, and paginates through its segment independently.
DynamoDB assigns items to segments by hashing the partition key, so segments can be uneven, and all items with one partition key land in the same segment. A parallel scan also consumes read capacity as fast as its workers can send requests. On a table that serves production traffic, give each worker a Limit and pace it. The Scan guide has a complete worker example.
Code examples
The examples use the orders table from above: partition key customerId, sort key orderDate (an ISO 8601 string), plus orderId, amount and status. status is a reserved word, so expressions refer to it through a placeholder.
JavaScript (AWS SDK v3)
Python (boto3)
boto3's Key and Attr helpers generate the attribute name placeholders themselves, so reserved words need no special handling here.
AWS CLI
The free query builder generates these parameters as SDK v3 code or a CLI command from a form.
Turn a frequent Scan into a Query with a GSI
If you scan for the same attribute again and again, index it. Say support staff look up orders by orderId, which isn't part of the table's key. Without an index, every lookup is a Scan with orderId = :id as the filter, reading the whole table to return one item. A global secondary index with orderId as its partition key turns it into a Query that reads one item:
The update-table call works as written on an on-demand table; on a provisioned table the new index also needs ProvisionedThroughput. DynamoDB backfills the index from existing items, and you can query it once its status is ACTIVE. The trade-offs: every write to the table is also written to the index, which costs write capacity and storage, and reads from a GSI are eventually consistent. Since November 2025 a GSI key can combine up to four attributes for the partition key and four for the sort key, which replaces hand-built composite keys such as TENANT#STATUS. The GSI guide covers projections and key design.
When a Scan is the right call
- The table is small. Reading a 1 MB configuration or lookup table in full costs 128 RCU with eventually consistent reads, and caching the result is simpler than designing an index for it.
- You need every item. One-off migrations, backfilling a new attribute, or re-keying data all have to touch everything. Use a parallel scan with a
Limitper worker, and run it off-peak. - You want a full copy or analytics. Export to S3 reads from the table's point-in-time recovery data and uses no read capacity at all (PITR must be on). For ongoing SQL analytics, the zero-ETL integrations replicate the table into Amazon Redshift, SageMaker Lakehouse or OpenSearch Service with no impact on the table's production workload.
- You're scanning a sparse index. A GSI only contains items that have its key attributes. An index keyed on an attribute you set only on pending orders, for example, stays small, and scanning it reads only those items.
A Scan in a request path, one that runs per API call or per user action, is almost always a design problem. If you scan from a desktop client, Dynobase shows the estimated read capacity of a full scan on the Scan button before you start, stops at match, page or time limits, and lets you resume an incomplete scan later.
Frequently Asked Questions
Is DynamoDB Query faster than Scan?
Is DynamoDB Query cheaper than Scan?
Does a FilterExpression reduce the cost of a Scan or Query?
ScannedCount with Count in the response to see how much was discarded.Can I query DynamoDB without the partition key?
Why does my Scan or Query return no items when matching items exist?
Limit items, before the filter runs, so a page can be empty and still return a LastEvaluatedKey. Keep sending requests with that value as ExclusiveStartKey until LastEvaluatedKey is absent. See DynamoDB pagination.What are dynamodb:Query and dynamodb:Scan in IAM?
dynamodb:Query without dynamodb:Scan, or adding an explicit deny for dynamodb:Scan, keeps a role from scanning tables. PartiQL SELECT statements are authorized by dynamodb:PartiQLSelect instead, which supports a dynamodb:FullTableScan condition key for blocking scans.Dynobase is a Professional GUI Client for DynamoDB
Start your 7-day free trial today
Product Features
DynamoDB Tools
- SSH GUI Client
- Chat With Cloud
- MongoDB GUI Client
- AWS Cost Monitoring
- DynamoDB Query Builder
- DynamoDB Code Examples
- DynamoDB Node.js Queries
- DynamoDB JSON Converter
- DynamoDB UUID Generator
- DynamoDB Local Admin GUI
- DynamoDB Pricing Calculator
- DynamoDB Single-Table Design
- Best DynamoDB GUI Clients
- Dynobase vs DynoTable