dynobase-icon
Dynobase

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

Rafal Wilinski

Written by Rafal Wilinski

Last updated: October 23rd, 2026

About to scan a large table? Dynobase estimates a scan's read usage before it runs, lets you set limits, and resumes scans that didn't finish. Scan controls in Dynobase

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

QueryScan
What it readsItems with one partition key value, optionally a sort key rangeEvery item in the table or index
Required inputEquality (=) on the partition key; optional sort key conditionOnly the table name
Read costTotal size of the items read, rounded up to the next 4 KBTotal size of every item evaluated, rounded up to 4 KB
TimeGrows with the resultGrows with the table
Data per requestUp to 1 MB, read before any filterUp to 1 MB, read before any filter
LimitCaps the items evaluated per request, before the filterSame
FilterExpressionNon-key attributes only; doesn't reduce costAny attribute, keys included; doesn't reduce cost
Result orderSorted by sort key; ScanIndexForward: false reverses itNo order across partition keys
ParallelismSend Queries for different keys concurrentlyBuilt-in parallel scan with Segment and TotalSegments
Read consistencyEventual by default; strong on tables and LSIs at twice the cost; GSIs are eventual onlySame
IndexesTables, local and global secondary indexes (IndexName)Same
IAM actiondynamodb:Querydynamodb:Scan

Which read operation to use

Decision tree for choosing a DynamoDB read: GetItem for one item with a known key, Query for items that share a partition key, BatchGetItem for known keys across partition keys, and Scan with a filter when no key is known

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:

ReadData readStrongly consistentEventually consistent
Query customerId = "c-1001"40 × 2 KB = 80 KB20 RCU10 RCU
Scan with filter customerId = :c1,000,000 × 2 KB = 2,000,000 KB500,000 RCU250,000 RCU
GetItem for one of those orders2 KB, rounded up to 4 KB1 RCU0.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 Limit per 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?

Yes, on any table larger than a few pages. A Query reads only the items under one partition key, so its time depends on the size of the result. A Scan reads the whole table at up to 1 MB per request, so its time grows with the table. A parallel scan shortens the wall-clock time but not the amount of data read.

Is DynamoDB Query cheaper than Scan?

Yes. Both charge for the data they read, in 4 KB units, and a Query reads only the items under the key you ask for while a Scan reads every item. Finding 40 orders of 2 KB costs about 10 RCU with a Query and about 250,000 RCU with a Scan of a one-million-item table, using eventually consistent reads.

Does a FilterExpression reduce the cost of a Scan or Query?

No. The filter is applied after the items are read, so consumed capacity is the same with or without it. It only reduces the data sent back to you. Compare ScannedCount with Count in the response to see how much was discarded.

Can I query DynamoDB without the partition key?

No. Query requires an equality condition on the partition key of the table or index you query. To look items up by another attribute, create a global secondary index with that attribute as its partition key and query the index; otherwise you need a Scan.

Why does my Scan or Query return no items when matching items exist?

Each request reads at most 1 MB, or 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?

They are the IAM actions for the two operations. Granting 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

Download
/
Changelog
/
Pricing
/
Member Portal
/
Privacy
/
EULA
/
Twitter
© 2026 Dynobase