DynamoDB mistakes are expensive — not in dollars, but in the hot partitions, full table scans, and impossible access patterns that result from wrong key design. The advanced patterns here — single-table design, GSI overloading, sparse indexes, and conditional writes — are what power the highest-scale production DynamoDB systems. This guide focuses on AWS DynamoDB, with the production trade-offs senior engineers need to make sound decisions.
⚡ TL;DR: Design access patterns first, then choose keys. Single-table: prefix PK/SK for different entity types. GSI for alternate access patterns. Sparse GSI: only items with GSI attribute are indexed. Conditional writes for optimistic locking. DynamoDB Streams trigger Lambda on changes. TTL for automatic expiry.
Single-table design patterns — AWS DynamoDB
// All entities in one table: prefix PK and SK
// Users and orders in same table:
// PK: USER#userId SK: METADATA → user entity
// PK: USER#userId SK: ORDER#orderId → user's order
// PK: ORDER#orderId SK: METADATA → order entity
// PK: ORDER#orderId SK: ITEM#itemId → order item
// Query all orders for a user:
const result = await client.send(new QueryCommand({
TableName: 'main',
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
ExpressionAttributeValues: {
':pk': 'USER#'+userId,
':prefix': 'ORDER#'
}
}));
// Query single order:
const order = await client.send(new GetItemCommand({
TableName: 'main',
Key: { PK: 'ORDER#'+orderId, SK: 'METADATA' }
}));GSI overloading — multiple access patterns from one GSI
// GSI1: GSI1PK, GSI1SK — overloaded for different entity types
// User entity:
// PK: USER#123 SK: METADATA
// GSI1PK: EMAIL#alice@example.com GSI1SK: USER#123
// → GSI1 enables: look up user by email
// Order entity:
// PK: ORDER#456 SK: METADATA
// GSI1PK: STATUS#pending GSI1SK: DATE#2026-04-01
// → GSI1 enables: query all pending orders by date
// Same GSI serves completely different entity lookups!
// Key: different entities use different value formats in GSI1PKConditional writes — optimistic locking
// Only update if version matches (prevents lost updates)
const updateResult = await client.send(new UpdateItemCommand({
TableName: 'inventory',
Key: { PK: 'PRODUCT#'+productId, SK: 'STOCK' },
UpdateExpression: 'SET stock = stock - :qty, version = version + :one',
ConditionExpression: 'stock >= :qty AND version = :v',
ExpressionAttributeValues: {
':qty': { N: '1' }, ':one': { N: '1' }, ':v': { N: String(currentVersion) }
}
}));
// If stock < qty or version changed: ConditionalCheckFailedException
// Retry with fresh version if optimistic lock failsDynamoDB Transactions + TTL
// ACID transactions across up to 25 items:
await client.send(new TransactWriteItemsCommand({
TransactItems: [
{ Update: { // Debit account
TableName:'accounts', Key:{PK:'ACCT#from'},
UpdateExpression:'SET balance = balance - :amt',
ConditionExpression:'balance >= :amt',
ExpressionAttributeValues:{':amt':{N:'100'}}
}},
{ Update: { // Credit account
TableName:'accounts', Key:{PK:'ACCT#to'},
UpdateExpression:'SET balance = balance + :amt',
ExpressionAttributeValues:{':amt':{N:'100'}}
}}
]
}));
// TTL: auto-delete expired items (free!)
// Store TTL as Unix timestamp in 'expiresAt' attribute
// Enable TTL: aws dynamodb update-time-to-live --ttl-attribute expiresAt- ✅ Single-table design for related entities with multiple access patterns
- ✅ GSI overloading: one GSI serves multiple entity lookup patterns
- ✅ Conditional writes for optimistic concurrency control
- ✅ TTL for automatic session/cache/token expiry
- ✅ Transactions for atomic multi-item updates
- ❌ Never start with DynamoDB before identifying access patterns
- ❌ Never scan large tables — redesign keys instead
External reference: Alex DeBrie Single-Table Design.
Recommended Reading
→ Designing Data-Intensive Applications — The bible of distributed systems and production engineering at scale.
→ The Pragmatic Programmer — Timeless engineering wisdom every senior developer needs.
Affiliate links. We earn a small commission at no extra cost to you.
Related on CheatCoders: DynamoDB single-table design, AWS Lambda best practices, and Bedrock Agents with production guardrails.
Free Weekly Newsletter
🚀 Join 2,000+ Senior Developers
Get expert-level JavaScript, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.
Related: AWS Clickstream Analytics at Massive Scale: Kinesis, Lambda, Firehose, S3, Athena, and OpenSearch in Production · How to Build a Hyper-Scale Serverless API Platform on AWS with API Gateway, Lambda, DynamoDB, and CloudFront · How to Build a Real-Time Fraud Detection and Decisioning Architecture on AWS with EventBridge, Step Functions, and DynamoDB
Related: Production IoT Telemetry on AWS: Kinesis, Lambda, DynamoDB, S3, and Event-Driven Device Command Workflows · How to Build a High-Scale Product Search and Catalog Platform on AWS with OpenSearch, DynamoDB, SQS, and EventBridge · Designing an Active-Active Global Collaboration Platform on AWS with API Gateway, EKS, DynamoDB Global Tables, and EventBridge
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Agentic Git Workflows: Atomic Commits From Noisy LLM Diffs
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
Newly added
- Amazon Bedrock Model Evaluation: Score Coding-Agent Outputs Before You Promote a Prompt
- AWS App Runner: Host Coding-Agent HTTP APIs Without Babysitting Containers
- Amazon S3 Object Lock: Immutable Artifact Buckets Coding Agents Cannot Overwrite
- AWS Security Hub: Aggregate Coding-Agent Security Findings Across Accounts
- Amazon Detective: Investigate Compromised Coding-Agent Credentials After GuardDuty
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
