PracHub
QuestionsLearningGuidesInterview Prep

DynamoDB Interview Guide: Data Modeling and Design Patterns

Prepare for DynamoDB interviews with key concepts, access patterns, indexes, migrations, consistency, capacity, hot keys, and system design trade-offs.

Author: PracHub

Published: 7/6/2026

Home›Knowledge Hub›DynamoDB Interview Guide: Data Modeling and Design Patterns

DynamoDB Interview Guide: Data Modeling and Design Patterns

By PracHub
July 6, 2026
17 min read
0

Quick Overview

This article explains DynamoDB fundamentals, primary keys, indexes, capacity modes, consistency, limits, and when to choose it over other storage systems. It also covers interview-focused data modeling, migration strategies, hot-key mitigation, SLA trade-offs, and system design patterns.

Free

DynamoDB Guide for Interviews: Basics, Data Modeling, Migrations, SLA Trade-offs, and System Design Patterns

DynamoDB is AWS's managed NoSQL key-value and document database. I reach for it when the workload is operational, high-volume, and the read/write paths are known up front. It gives predictable latency without making you run shards, replicas, backups, or failover yourself. It is not the thing I'd pick for joins, ad hoc analytics, full-text search, or "let users filter on any field" requirements.

In interviews, don't stop at "DynamoDB scales." Say what you would actually build: "Here are the access patterns, keys, indexes, consistency choices, capacity numbers, and hot-key mitigations."

1. What DynamoDB Is—and When Interviewers Expect You to Use It

A DynamoDB table stores items. Every item has a primary key:

  • Partition key only: good for point lookups.
  • Partition key + sort key: good for item collections and ordered/range queries.

The API constraint that drives most designs: a Query must specify partition-key equality. The sort key can have conditions like =, <, <=, >, >=, BETWEEN, and begins_with. If a field is not part of the table key or an index key, DynamoDB cannot efficiently query it. You are scanning.

DynamoDB supports key-value and document-style items, conditional writes, TTL, Streams, transactions, backups, global replication, and PartiQL. PartiQL gives you SQL-like syntax. It does not give you relational joins or a traditional query optimizer.

Limits worth having in your head:

  • Maximum item size: 400 KB.
  • Query/Scan page: up to 1 MB before filters.
  • BatchWriteItem: max 25 put/delete requests; no updates or conditions.
  • BatchGetItem: max 100 items.
  • Batch request/response size: 16 MB.
  • Transactions: up to 100 unique items, 4 MB total, no multiple operations on the same item.
  • LSIs: created with the table; 10 GB item-collection limit per partition key when LSIs exist.
  • Default index quotas commonly matter: 5 LSIs and 20 GSIs per table.
  • A physical partition has rough design ceilings around 1,000 1 KB writes/sec or 3,000 strongly consistent 4 KB reads/sec; eventually consistent reads can be higher. Table-level capacity does not fix one extremely hot logical key.

Logical partition keys map to physical partitions. DynamoDB has adaptive capacity and split-for-heat, which help with uneven traffic, but they do not save every design. One hot key, an LSI-constrained item collection, or monotonically increasing writes under one key can still throttle.

Pricing depends on capacity mode, item size, reads/writes, storage, indexes, backups, streams, exports, and global-table replicated writes:

  • On-demand: easier for unknown or spiky traffic, but not infinite. Big launches may need pre-warming or quota increases.
  • Provisioned: better when traffic is predictable and cost control matters; autoscaling reacts after load shows up.

Basic setup choices:

aws dynamodb create-table \
  --table-name MainTable \
  --attribute-definitions AttributeName=PK,AttributeType=S AttributeName=SK,AttributeType=S \
  --key-schema AttributeName=PK,KeyType=HASH AttributeName=SK,KeyType=RANGE \
  --billing-mode PAY_PER_REQUEST

For local development, use DynamoDB Local or a local AWS-compatible stack. Create tables during test setup and run integration tests against the local endpoint. In the console, you create the table, choose the key schema, capacity mode, indexes, TTL, Streams, encryption, backups, and alarms.

Storage choices:

NeedBetter toolDynamoDB role
User/order/session stateDynamoDBDurable serving state
SQL joins/constraintsRDS-style databaseMaybe source of truth or reporting
Flexible JSON queryMongoDB-style databasePrefer if many fields are queried ad hoc
Cassandra-like wide writesCassandra/managed equivalentPrefer if you need Cassandra ecosystem semantics
Hot repeated readsRedis/cacheCache DynamoDB results; handle invalidation
Blobs/photos/videosObject storageStore metadata and object pointers
Text search/rankingSearch indexFeed via Streams
Async workflowsQueue/streamDynamoDB stores job state

2. DynamoDB Data Modeling Starts with Access Patterns

Before choosing keys, turn requirements into an access-pattern table:

Access patternFrequencySort/filterConsistencyPage size
Get user profilehighby user IDstrong if just written1
List user ordershighnewest first, by timeeventual OK20
Get order by IDhighexact IDstrong if base-key read1
Dashboard pending ordersmediumstatus + timeeventual OK100
Update order statushighidempotent transitionconditional writen/a

Composite keys are where a lot of DynamoDB bugs come from. Use ISO-8601 timestamps for lexical time order, zero-pad numbers (ORDER#000010), normalize case, escape delimiters in user-provided values, and avoid mistakes like ORDER#10 sorting before ORDER#2.

Worked order design:

Access patternTable/indexKey
Get profilebasePK=USER#u1, SK=PROFILE
List orders for userbasePK=USER#u1, SK=ORDER#2026-01-10T10:00:00Z#o9
Get order by IDGSI1GSI1PK=ORDER#o9, GSI1SK=META
Pending dashboardGSI2GSI2PK=STATUS#PENDING#2026-01-10#B03, GSI2SK=CREATED#...#ORDER#o9

Example item:

{
  "PK": "USER#u1",
  "SK": "ORDER#2026-01-10T10:00:00Z#o9",
  "entityType": "Order",
  "orderId": "o9",
  "status": "PENDING",
  "totalCents": 2450,
  "GSI1PK": "ORDER#o9",
  "GSI1SK": "META",
  "GSI2PK": "STATUS#PENDING#2026-01-10#B03",
  "GSI2SK": "CREATED#2026-01-10T10:00:00Z#ORDER#o9"
}

Concrete paginated query with the AWS SDK:

from boto3.dynamodb.conditions import Key

def list_user_orders(table, user_id, limit=20, start_key=None):
    resp = table.query(
        KeyConditionExpression=Key("PK").eq(f"USER#{user_id}") &
                               Key("SK").begins_with("ORDER#"),
        ScanIndexForward=False,
        Limit=limit,
        ExclusiveStartKey=start_key
    )
    return resp["Items"], resp.get("LastEvaluatedKey")

Use ConsistentRead=True only on base-table or LSI queries when read-after-write matters. GSIs are eventually consistent, so a write followed immediately by a GSI lookup can miss the item.

GSI projection changes both cost and latency:

  • KEYS_ONLY: cheapest, often requires a second base-table read.
  • INCLUDE: project selected fields.
  • ALL: simpler reads, more storage and write cost.

For status dashboards, date buckets and shard buckets keep one STATUS#PENDING key from getting hot. The trade-off is application work. A dashboard covering seven days has to query seven day buckets times N shards, merge the results, sort them, and paginate outside DynamoDB.

Status changes update indexed attributes. The old GSI entry disappears after index propagation, not at the exact instant your write returns. Dashboards can briefly show stale data. Make status updates idempotent, include a version, and either tolerate the lag or verify with a base-table read before taking critical action.

Single-table design works well when access patterns share indexes and item collections. Multi-table design is often cleaner for separate teams, IAM boundaries, backup/restore granularity, tenant migration, table-level throttling isolation, or when single-table modeling is more clever than useful.

3. Secondary Indexes, Query Patterns, and Common Modeling Mistakes

Index mechanics:

  • LSI: same partition key, different sort key, strong reads possible, created at table creation, 10 GB item-collection limit.
  • GSI: independent key schema, eventually consistent, can be added later, has its own throughput behavior.

Indexes duplicate storage. A base-table write also writes the projected GSI attributes. Large projections add storage and WCU cost. If a GSI is under-provisioned or hot, it can back-pressure base-table writes. Sparse indexes reduce storage, write cost, backfill size, and the number of indexed items queried; "faster" mostly comes from indexing less data.

Expressions:

  • KeyConditionExpression: efficient key lookup; must include partition-key equality.
  • FilterExpression: applied after reading; does not reduce consumed read capacity.
  • ProjectionExpression: reduces returned attributes, not necessarily consumed capacity for table reads.
  • Watch reserved words; use expression attribute names. Use ADD for atomic counters, list_append carefully, conditions for race safety, and ReturnValues or ReturnValuesOnConditionCheckFailure to avoid extra reads.

Scans are fine for tiny admin tables, offline maintenance, exports, backfills, parallel scan jobs, and rare operator tools. They do not belong on hot user-facing paths.

A bad design is not "multiple tables." It is bad when the access patterns do not have supporting keys or indexes:

Orders: PK=order_id only

That table cannot list orders by user or status without a GSI or a scan.

Complete item collection example:

PK=USER#u1,  SK=PROFILE
PK=USER#u1,  SK=ORDER#2026-01-10T10:00:00Z#o9
PK=ORDER#o9, SK=META
PK=ORDER#o9, SK=ITEM#000001#sku1
PK=ORDER#o9, SK=PAYMENT#p1
GSI1PK=ORDER#o9, GSI1SK=META

Many-to-many relationships use inverted items:

PK=GROUP#g1, SK=USER#u1
PK=USER#u1,  SK=GROUP#g1

Uniqueness needs conditional claim items, not just a GSI:

table.put_item(
    Item={"PK": "UNIQUE#EMAIL#a@example", "SK": "CLAIM", "userId": "u1"},
    ConditionExpression="attribute_not_exists(PK)"
)

Counters and leaderboards are easy to get wrong. One global counter becomes a hot key; use sharded counters and aggregate periodically. For top-N, maintain precomputed leaderboard buckets instead of sorting the whole table.

4. Designing a Relational-to-DynamoDB Migration System

Migration prompts are about correctness first. Throughput matters, but a fast divergent migration is still a failed migration.

Plan:

  1. Inventory SQL tables, transactions, foreign keys, writes, and reads.
  2. Define DynamoDB access patterns and denormalized items.
  3. Choose source of truth during migration.
  4. Backfill from a consistent snapshot.
  5. Replay CDC.
  6. Validate invariants.
  7. Cut over by tenant, region, or API.
  8. Keep a real rollback path.

CDC options include MySQL binlog, Postgres WAL, managed migration tooling, Kafka, or an outbox table. Record a high-watermark such as LSN, binlog position, transaction ID, or monotonic source version. Prefer log sequence/version ordering over wall-clock commit timestamps.

Snapshot-plus-CDC race handling:

  • Start CDC capture.
  • Record high-watermark.
  • Export snapshot as of that point.
  • Backfill idempotently.
  • Replay CDC events at or after the watermark.
  • Use source versions so old snapshot rows cannot overwrite newer CDC changes.

Relational transactions spanning orders, payments, and order_items may produce several DynamoDB items. Preserve commit order and avoid partially materialized aggregates by consuming the transaction log as transactions, or by building aggregates only after all rows for a source transaction arrive.

Validation examples:

  • SQL order count equals DynamoDB OrderMeta count.
  • Sum of item totals equals order total.
  • Every payment item has an order meta item.
  • Deleted SQL rows produce DynamoDB tombstones/deletes.
  • Sampled API responses match, not just raw row checksums.

Naive dual writes cause pain: SQL succeeds and DynamoDB fails, or the reverse, and now the systems disagree. Prefer SQL write plus outbox/CDC until cutover. If DynamoDB becomes the source of truth, rollback to SQL requires reverse replication or an outbox feeding SQL; read routing alone is not rollback.

Backfill worker sketch:

def batch_put(ddb, table, items):
    pending = [{"PutRequest": {"Item": item}} for item in items]  # max 25
    delay = 0.05
    while pending:
        resp = ddb.batch_write_item(RequestItems={table: pending[:25]})
        pending = resp.get("UnprocessedItems", {}).get(table, []) + pending[25:]
        if pending:
            sleep_with_jitter(delay)
            delay = min(delay * 2, 2.0)

Throttle workers, watch table and GSI consumption, and write idempotent keys. BatchWriteItem does not support conditions, so use deterministic overwrites only when that is safe; otherwise use PutItem with conditions.

5. DynamoDB in Object Stores, Dedup Systems, and Metadata-Heavy Designs

DynamoDB stores metadata, not blobs:

ItemPKSK
File metadataTENANT#t1#FILE#f1META
Visible user listingTENANT#t1#USER#u1COMMITTED#2026-01-10#f1
Hash claimTENANT#t1#HASH#sha256CLAIM
Chunk manifestTENANT#t1#FILE#f1CHUNK#000001
Object referenceTENANT#t1#OBJECT#obj9REF#FILE#f1

Do not create the visible listing until commit. Another option is to encode commitment in the sort key, as shown above. Filters are not enough if pending files share the listing key.

Race-free exact dedup claim:

ddb.transact_write_items(TransactItems=[
  {"Put": {
    "TableName": "MainTable",
    "Item": {"PK": {"S": "TENANT#t1#HASH#abc"}, "SK": {"S": "CLAIM"},
             "objectId": {"S": "obj9"}},
    "ConditionExpression": "attribute_not_exists(PK)"
  }},
  {"Put": {
    "TableName": "MainTable",
    "Item": {"PK": {"S": "TENANT#t1#OBJECT#obj9"}, "SK": {"S": "META"},
             "state": {"S": "COMMITTED"}}
  }}
])

SHA-256 exact dedup still needs a collision-handling policy, usually comparing size and optionally bytes. Perceptual photo hashes are similarity signals, not proof. They need thresholds and product tolerance for false positives.

Do not put all reference counting on one hot item. Use reference items, sharded counters, async compaction, and garbage collection that deletes objects only after no references remain for a grace period.

Object storage and DynamoDB do not share a transaction. Write bytes first, commit metadata conditionally, and run cleanup scans for orphaned blobs or pending metadata. Large manifests should be paginated as CHUNK#000001; never grow one manifest item toward 400 KB. Retrieve large files by querying chunk pages, then fetching chunks in parallel while preserving order.

6. Protecting SLAs with DynamoDB, Caches, Queues, and Async Workflows

DynamoDB-specific SLA tactics:

  • Use eventually consistent reads unless read-after-write requires strong base-table reads.
  • Avoid transactional reads/writes in hot paths unless the invariant needs them.
  • Use conditional writes for admission control, quotas, state transitions, and idempotency.
  • Handle throttling exceptions with bounded retries, jittered backoff, deadlines, and fallback.
  • Set SDK timeouts; do not let retries exceed API latency budgets.

Caches need explicit invalidation rules. Pick TTLs based on stale-read tolerance, handle negative caching carefully, prevent write races with versions, and invalidate through application events or DynamoDB Streams. Cache-aside is fine when stale reads are acceptable. Write-through helps when freshness matters and write volume is manageable.

Queues need idempotent consumers. Expect duplicate delivery, visibility timeout expirations, poison messages, DLQs, lag, and out-of-order processing unless you design partitioning/ordering keys for it. Store user-facing job status in DynamoDB so queue lag shows up as PENDING or RUNNING.

TTL deletion is asynchronous. Do not rely on TTL alone for hard expiration of locks, sessions, or idempotency windows; check expiration timestamps in application logic.

Security basics: least-privilege IAM, encryption, VPC endpoints where appropriate, tenant-prefixed keys, tests for cross-tenant key bugs, and IAM conditions such as leading-key restrictions when they match your key design.

7. Consistency, Transactions, Idempotency, and Failure Modes

Consistency options:

  • Base table and LSI reads can be strongly consistent within a Region.
  • GSIs are eventually consistent.
  • Standard global tables replicate asynchronously and resolve conflicts with last-writer-wins semantics.
  • Multi-Region strong consistency is available only when specifically configured and supported; otherwise design for eventual convergence.

Transactions cost more capacity, add latency, have the 100-item/4 MB limits, cannot operate twice on the same item, and reduce availability compared with single-item writes.

For async creation, do not commit DynamoDB and then enqueue a message outside the transaction with no recovery path. Put an outbox item in the same transaction. A worker can read Streams or poll the outbox and enqueue.

def create_job(ddb, tenant, request_id, payload_hash):
    job_id = deterministic_id(tenant, request_id)
    try:
        ddb.transact_write_items(TransactItems=[
          {"Put": {"TableName": "MainTable",
            "Item": {"PK": {"S": f"TENANT#{tenant}#IDEMP#{request_id}"},
                     "SK": {"S": "REQUEST"}, "jobId": {"S": job_id},
                     "payloadHash": {"S": payload_hash}},
            "ConditionExpression": "attribute_not_exists(PK)"}},
          {"Put": {"TableName": "MainTable",
            "Item": {"PK": {"S": f"TENANT#{tenant}#JOB#{job_id}"},
                     "SK": {"S": "META"}, "status": {"S": "PENDING"},
                     "version": {"N": "1"}},
            "ConditionExpression": "attribute_not_exists(PK)"}},
          {"Put": {"TableName": "MainTable",
            "Item": {"PK": {"S": f"TENANT#{tenant}#OUTBOX#{job_id}"},
                     "SK": {"S": "ENQUEUE"}, "state": {"S": "NEW"}}}}
        ])
        return job_id, "created"
    except ddb.exceptions.TransactionCanceledException:
        # Read idempotency record strongly; if job missing, repair or return 409 for operator recovery.
        return recover_existing_request(tenant, request_id, payload_hash)

Use one key format consistently. State transitions should be conditional:

Update PK=TENANT#t1#JOB#j1, SK=META
SET status=RUNNING, version=version+1
IF status=PENDING AND version=1

Streams are central for outbox processing, materialized views, cache invalidation, audit logs, and search indexing. Stream records are ordered per partition key and delivered at least once. Consumers must handle retries, duplicates, poison records, and iterator age.

8. Capacity Planning, Cost, Hot Partitions, and Operational Readiness

Capacity formulas:

  • Write: round item size up to 1 KB. A 2 KB write costs 2 WCUs on the base table, plus each GSI's projected item size.
  • Strong read: round up to 4 KB per RCU.
  • Eventually consistent read: half the strong-read cost.
  • Transactional reads/writes cost roughly double.
  • Queries consume capacity for bytes read before filters.

Example: 1,000 writes/sec of 2 KB items with two GSIs projecting 1 KB each:

Base: 1,000 * 2 = 2,000 WCUs
GSI1: 1,000 * 1 = 1,000 WCUs
GSI2: 1,000 * 1 = 1,000 WCUs
Total ≈ 4,000 WCUs, before transactions or retries

Query example: 20 items × 2 KB = 40 KB. Strong read ≈ 10 RCUs; eventual read ≈ 5 RCUs.

Hot-key risks:

  • PK=STATUS#PENDING
  • one huge TENANT#id
  • GSI1PK=DATE#today
  • all writes under one key with increasing timestamp sort keys

Mitigate with shard suffixes, tenant subpartitioning, queues, caches, and precomputed views. Adaptive capacity helps uneven traffic, and split-for-heat can help sustained hot ranges, but LSIs and monotonically increasing sort keys can limit splitting.

Operational checklist:

  • Alarms for throttles, latency, errors, consumed capacity, GSI throttles, account/table quotas, max on-demand caps.
  • Contributor Insights for hot keys.
  • SDK retry/timeout metrics and conditional-check failure rates.
  • Stream consumer failures, DLQs, iterator age.
  • PITR, backups, restore drills, and tenant-restore plans.
  • Chaos/load tests with skewed tenants, not uniform IDs.
  • Runbooks for GSI backfill, throttling, deletes, and rollback.

Single-table backup/restore can be awkward in production. Restoring one entity type or one tenant from a shared table may require export, filtering, and replay into a new table.

Deletes remove base items and eventually remove GSI entries. TTL deletes are delayed. For migrations and async processors, tombstones may be safer than hard deletes until all consumers have observed them. For privacy deletion, delete all duplicated copies, index entries, object references, cache entries, and downstream search documents.

9. How to Structure a DynamoDB System Design Interview Answer

Use this framework:

  1. Clarify latency, consistency, durability, retention, tenants, and scale.
  2. Turn requirements into access patterns with frequency, sort order, filters, consistency, and page size.
  3. Design table keys, GSIs, sparse indexes, and projections.
  4. Show exact queries and pagination.
  5. Explain writes, conditions, idempotency, deletes, and transactions.
  6. Estimate capacity and hot keys.
  7. Cover caches, queues, Streams, rollback, security, and operations.

Example prompt: "Design order status storage for a food delivery service."

Answer outline:

Requirements:
- Create/update order status.
- User lists recent orders.
- Support lookup by order ID.
- Ops dashboard lists pending orders.
- Peak: 1,000 order writes/sec, 20-item user queries.

Table:
- PK=USER#id, SK=ORDER#timestamp#orderId for user history.
- Duplicate order meta at PK=ORDER#id, SK=META for strong point reads.
- GSI_STATUS: status/date/shard key for dashboard.

Queries:
- Get order: GetItem PK=ORDER#id SK=META, strong if needed.
- List user orders: Query PK=USER#id and begins_with(SK,'ORDER#'), limit 20.
- Dashboard: query N date buckets × shard buckets, merge by createdAt.

Writes:
- Conditional version update: only transition from valid previous status.
- Idempotency item per request.
- Status GSI lag acceptable for dashboard; critical actions verify base item.

Capacity:
- 1,000 writes/sec × 2 KB base + two 1 KB index writes ≈ 4,000 WCUs.
- User query 40 KB eventual ≈ 5 RCUs.
- Shard pending status keys to avoid one hot partition.

Failure:
- Retry throttles with jitter.
- Streams update cache/search.
- DLQ failed processors.
- PITR and restore drill.

Prompt variations:

PromptHow answer changes
Marketplace migrationemphasize snapshot, CDC, invariants, rollback
Object storeDynamoDB metadata, object storage blobs, manifest pagination
SLA protectioncache invalidation, queues, timeouts, graceful degradation
Async CRUD APIidempotent job table, outbox, Streams, duplicate consumers
Social graphmany-to-many inverted items, hot celebrity sharding

FAQ

Is DynamoDB SQL or NoSQL?

DynamoDB is NoSQL. It supports PartiQL, but not relational joins, foreign keys, or arbitrary SQL query planning.

How do you choose a DynamoDB partition key?

Match the key to the access pattern. ORDER#id works well for point reads, but it cannot list all tenant orders without another key or GSI. TENANT#id supports tenant listing but may create noisy-neighbor hot keys. Large tenants may need shard suffixes or resource-level partitioning.

When should you use a GSI?

Use a GSI for an alternate query path, not as a uniqueness constraint. Enforce uniqueness with conditional claim items or transactions. Remember that GSIs are eventually consistent and add write/storage cost.

When are scans acceptable?

Small admin data, backfills, exports, parallel offline jobs, and maintenance. They are usually wrong for user-facing request paths.

Is DynamoDB strongly consistent?

Base tables and LSIs can be strongly consistent within a Region. GSIs are eventually consistent. Global-table behavior depends on the configured replication mode.


Comments (0)


Related Articles

From Non-CS Major to Software Engineer: A Practical Guide to Cracking the Technical Interview

Prepare for technical interviews with a practical guide to DSA practice, live coding, mock interviews, communication, and interview mindset.

Software Engineer

From Non-CS Major to Software Engineer: A Practical Guide to Cracking the Technical Interview

Prepare for technical interviews with a practical guide to DSA practice, live coding, mock interviews, communication, and interview mindset.

Software Engineer

Design WhatsApp: the presence and receipt problems most candidates ignore

Design WhatsApp-style chat with WebSockets, offline inboxes, Kafka partitions, presence TTLs, receipts, and reliable delivery.

Software Engineer

I Pinned Our Autoscaler for a Month to See What Would Break. Nothing Did.

Learn when Kubernetes autoscaling helps, when CPU-based HPA wastes money, and how capacity planning can cut cloud costs safely.

Software Engineer
PracHub

Master your tech interviews with 8,500+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • AI Coding Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.