DynamoDB Scan in Node.js (AWS SDK v3)

The do/while below is not defensive coding. A filtered Scan page can come back with an empty Items array and still have more of the table left, so stopping at the first response is how a scan reports zero matches on a table that has them. Query vs. Scan covers when to avoid the operation entirely.

Code

import {DynamoDBClient, ScanCommand} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({region: 'us-east-1'});

const items = [];
let lastEvaluatedKey;

do {
  const response = await client.send(
    new ScanCommand({
      TableName: 'Music',
      FilterExpression: '#filter0 >= :filterValue0',
      ExpressionAttributeNames: {
        '#filter0': 'Year'
      },
      ExpressionAttributeValues: {
        ':filterValue0': {N: '2010'}
      },
      ExclusiveStartKey: lastEvaluatedKey
    })
  );

  items.push(...(response.Items ?? []));
  lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);

console.log(`Matched ${items.length} items`);

Two empty pages, 284.5 read units, 8 items

The fixture is 600 songs of roughly 3.9 KB each, of which exactly 8 have Year >= 2010, and they sort last. Here is what the loop above actually receives:

Round tripItems.lengthScannedCountRead unitsLastEvaluatedKey
10271128.5set
20271128.5set
385827.5absent

Two consecutive pages return nothing and cost 128.5 read units each. Code that does if (!response.Items.length) return reports an empty table. The API reference states the rule plainly: "a scan result can result in no items meeting the criteria and the Count will result in zero", and separately that "a FilterExpression is applied after the items have already been read; the process of filtering does not consume any additional read capacity units".

Read that second sentence the way your invoice does. The filter is free, and everything it discarded is not: 284.5 read units to deliver 8 items, the same bill you would pay with no filter at all.

Measured 2026-07-28 against DynamoDB Local (amazon/dynamodb-local) with @aws-sdk/client-dynamodb 3.1095.0 on node v24.18.0. Counts and capacity are the engine's own response fields.

Explanation

  • ExclusiveStartKey: lastEvaluatedKey is undefined on the first pass. The v3 serialiser drops undefined members, so one object literal covers the first request and every follow-up. Passing {} instead fails with ValidationException: The provided starting key is invalid.
  • response.Items ?? [] — doing real work. Combine it with the table above: the nullish coalescing keeps the accumulator honest on the pages that matched nothing, and the while keeps the loop alive past them.
  • #filter0 is not decoration. Year is on AWS's reserved-word list, and using it unaliased returns ValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year.
  • Limit — counts items read, not items returned. With this filter, Limit: 10 yields Count: 0 and ScannedCount: 10. It is a throttling knob for capacity spikes, not a way to ask for ten results.
  • Segment / TotalSegments split a full-table scan across workers. That divides the wall clock, not the cost — the same 284.5 units are spent, just faster and more concurrently.

What that costs on a real table

284.5 read units for 8 items is the shape of the problem, and it scales linearly with the table, not with the result. Before shipping a filtered scan on a hot path, price the full-table read at your item size and traffic in the DynamoDB pricing calculator, then compare it to a GSI that turns the same access pattern into a Query.

To explore tables in a GUI, with filtered and paginated result grids, download DynoTable instead of scanning blind from a script.

References

Build this request visually

Compose this operation in the free DynamoDB Query Builder — key condition, filter, index, Limit, sort order, and a pagination loop — and copy it back as a runnable SDK v3, CLI, or boto3 program.

Open the DynamoDB Query Builder

Work with DynamoDB without the Console

A fast DynamoDB desktop client that runs the real SQL DynamoDB can’t — JOINs, GROUP BY, aggregates — with visual editing and an AI agent on your own Bedrock keys.

Free 30-day trial, no credit card — then the Free plan with no time limit.