List APIs return pages, and code that ignores pagination silently processes only the first 1000 items.
Most AWS list operations return results in pages: up to 1000 keys from list_objects_v2, 100 items from many others, with a continuation token (NextContinuationToken, NextToken, Marker; it varies). Writing the token loop yourself is error-prone.
A paginator handles it: client.get_paginator("list_objects_v2").paginate(Bucket=..., Prefix=...) returns an iterable of pages; loop over pages, then over items in each.
PaginationConfig={"MaxItems": 500, "PageSize": 100}caps the total and page size.page_iterator.search("Contents[?Size >1000].Key")applies a JMESPath expression across all pages and yields matching values lazily.client.can_paginate("list_objects_v2")tells you whether a paginator exists.
Resource collections (bucket.objects.filter(Prefix=...)) paginate automatically too.
A waiter polls until a resource reaches a state: client.get_waiter("bucket_exists").wait(Bucket=...), or for EC2 instance_running, for DynamoDB table_exists, for CloudFormation stack_create_complete. WaiterConfig={"Delay": 5, "MaxAttempts": 40} tunes polling; a WaiterError is raised on failure or timeout. client.waiter_names lists what's available.
Waiters are for scripts and infrastructure automation. In request-handling code, prefer event-driven designs to blocking polls.
Example
import boto3
s3 = boto3.client("s3", region_name="us-east-1")
s3.create_bucket(Bucket="demo-pages-2026")
s3.get_waiter("bucket_exists").wait(Bucket="demo-pages-2026", WaiterConfig={"Delay": 1, "MaxAttempts": 10})
for i in range(2500):
s3.put_object(Bucket="demo-pages-2026", Key=f"logs/{i:05}.txt", Body=b"x" * (i % 3))
first = s3.list_objects_v2(Bucket="demo-pages-2026", Prefix="logs/")
print(first["KeyCount"], first["IsTruncated"]) # 1000 True: only page 1!
pages = s3.get_paginator("list_objects_v2").paginate(Bucket="demo-pages-2026", Prefix="logs/")
total = sum(len(page.get("Contents", [])) for page in pages)
print(total) # 2500
pages = s3.get_paginator("list_objects_v2").paginate(Bucket="demo-pages-2026", Prefix="logs/")
print(sum(1 for _ in pages.search("Contents[?Size > `1`].Key"))) # 833Remember
- List APIs return pages; use get_paginator(...).paginate(...)
- PaginationConfig caps items and page size
- .search('JMESPath') filters across all pages lazily
- Waiters poll until a resource reaches a state
Watch out for
- Calling list_objects_v2 once and assuming you got every key.