You add an id = 12093 filter to a DynamoDB Scan and get three scores. A Query also returns three scores. Are they doing the same amount of work? The returned rows may match while the read paths differ. Filtering down to three visible rows is not the same as starting from only that student's key.
Where do Query and Scan start reading?
Assume StudentScore has partition key id (student ID) and sort key subject. This small example holds six items:
| id | subject | score |
|---|---|---|
| 12093 | English | 80 |
| 12093 | Science | 92 |
| 12093 | Math | 95 |
| 12103 | English | 100 |
| 12103 | Science | 88 |
| 12103 | Math | 82 |
Query uses id = 12093 as its key condition. It need not read the other student's items to locate this partition. Scan reads through a table or index, then applies FilterExpression after reading. In a single-page illustration, “6 read → 3 returned” describes these six example items; it is not a measured RCU value.
The diagram separates items evaluated from items returned. Query begins at the specified partition key; Scan evaluates the example's six items and returns three after filtering.
Why does a filter not save the read capacity?
The filter selects what leaves the read operation. AWS states that Scan consumes the same read capacity with or without a filter when it reads the same data. A ProjectionExpression can shrink the returned attributes, but it does not undo the capacity used to read the items.
Do not turn this example's 6 vs 3 item counts into an automatic 2× RCU claim. Item size, consistency, page boundaries, and billing granularity affect actual capacity. Inspect ScannedCount (evaluated items), Count (returned items), and ConsumedCapacity together.
A Scan reads up to 1 MB before applying a filter. A page can return Count: 0 yet still have a LastEvaluatedKey; keep paginating before concluding that no matching student exists.
How do you query by student ID instead?
With the stated key design, put student ID in the key condition, not just a filter. These AWS CLI examples require a real StudentScore table; they are illustrative commands, not output from a live account:
aws dynamodb query \
--table-name StudentScore \
--key-condition-expression 'id = :student' \
--expression-attribute-values '{":student":{"N":"12093"}}' \
--return-consumed-capacity TOTAL
aws dynamodb scan \
--table-name StudentScore \
--filter-expression 'id = :student' \
--expression-attribute-values '{":student":{"N":"12093"}}' \
--return-consumed-capacity TOTALAWS's Query key-condition rules require an equality condition on one partition-key value. You may add a condition on subject when needed. If the frequent access pattern instead asks for Math scores ordered by score, the existing id partition cannot provide that starting point. A GSI with subject as partition key and score as sort key is one possible design. It adds write and storage cost, and GSI reads are eventually consistent.
When is Scan appropriate?
A table-wide administrative or batch task may intentionally use Scan. But an API that runs Scan + filter for each student request evaluates unnecessary items as data grows. Write down the frequent lookup values and check whether they are partition keys in a table or index.
For a genuine full scan, follow LastEvaluatedKey through every page and monitor capacity and effect on other requests. A fast development-table scan does not predict production latency or cost.
Key takeaways
Query starts from a partition-key value. Scan reads items before filtering its returned results. The same three returned rows can therefore represent different read work. Inspect ScannedCount, Count, consumed capacity, and pagination; for recurring lookups, reconsider the key or index design before adding more filters.
For another example of where a condition is applied, see SQL WHERE vs HAVING. SQL aggregation and DynamoDB access paths are different mechanisms, so the analogy stops at the importance of the condition's stage.

