DynamoDB
A managed key-value and document store for known access patterns, where key design gives low latency without operating shards.
Also worth naming: Amazon DynamoDB · DynamoDB Global Tables (multi-region) · DAX (in-memory accelerator)
DynamoDB gets you ready to model queries before tables. Use it when the access pattern is simple and large enough that managed scaling matters.
What it is
DynamoDB is AWS's managed NoSQL database — a key-value and document store that scales horizontally and automatically, with no nodes or shards for you to operate and far less capacity planning than a self-managed store. You get single-digit-millisecond reads and writes whether the table holds a megabyte or a petabyte, because DynamoDB transparently spreads data across internal partitions and adds more as you grow.
Every item lives under a primary key: either a simple partition key, or a composite of a partition key + a sort key. The partition key is hashed to decide which partition stores the item; the sort key orders items within a partition. That structure is the whole game — it makes "get this exact item" and "get all items for this partition key, ordered/ranged by sort key" extremely fast, and makes anything else (arbitrary filters, joins, ad-hoc queries) slow or impossible without extra indexes.
The interview framing: DynamoDB is the right call when the access patterns are known, key-based, and high-scale — a URL shortener's lookups, a user's session store, a shopping cart, a feed keyed by user. It is the wrong call when you need rich ad-hoc queries, joins, or strong multi-row transactions across a large relational model — that is Postgres. You don't choose DynamoDB to avoid SQL; you choose it because your queries are simple and your scale is large.
When to reach for it
Reach for this when…
- Access patterns are known up front and are key-based (get/put by id, query by partition key)
- You need massive, elastic scale with predictable low latency and zero ops
- Write-heavy or spiky workloads where managing your own sharding would be painful
- Simple item shapes: sessions, carts, user profiles, feeds, event records, idempotency keys
Not really this pattern when…
- You need ad-hoc queries, joins, or aggregations across the data (that is SQL / Postgres)
- Access patterns are unknown or will change a lot — you cannot pre-design the keys
- You need rich multi-row ACID transactions across a complex relational model
- Full-text search or analytics — pair with Elasticsearch / a warehouse instead
How it works
Three ideas explain how to use DynamoDB well:
1. The partition key decides everything. DynamoDB hashes the partition key to place an item, so items with the same partition key live together and are retrieved together. Choose a partition key that (a) matches your dominant query and (b) spreads load evenly. A low-cardinality or skewed key creates a hot partition — one partition taking disproportionate traffic — which throttles regardless of total table capacity.
DynamoDB hashes the partition key to place an item on one of many internal partitions. Items that share a partition key are stored together, ordered by sort key — which is what makes "all items for this user, newest first" a single fast query.
2. You model access patterns, not entities. In SQL you normalise and join at read time. In DynamoDB you do the opposite: figure out every query you need, then design keys (and often a single table with overloaded keys) so each query is a single GetItem or Query. Denormalisation and duplication are expected — storage is cheap, and the goal is one fast lookup per access pattern.
3. Secondary indexes buy you more access patterns — at a cost. A Global Secondary Index (GSI) re-partitions the same items under a different key so you can query a second pattern (e.g. look up a user by email when the table is keyed by user_id). A GSI is a separate, asynchronously-maintained copy with its own capacity and eventual consistency — powerful, but not free.
A GSI re-partitions the same items under a new partition key so you can query a second access pattern. It is maintained asynchronously (eventually consistent) and has its own provisioned capacity — it is a copy, not a free index.
Consistency and capacity round it out: reads are eventually consistent by default (cheaper, lower latency) or strongly consistent on request within a region. On global tables, the default MREC mode is active-active and eventually consistent across Regions with last-writer-wins conflict resolution; MRSC adds multi-Region strong reads for stricter cases at higher latency and with regional constraints. Capacity is on-demand (pay per request, auto-scales — the usual 2026 interview default after AWS cut on-demand and global-table write pricing) or provisioned (RCU/WCU with auto scaling, still useful for steady high-utilisation tables). For a known extreme event, check warm throughput and pre-warm if the table/index is not ready for the spike.
Performance envelope
DynamoDB performance envelope — order-of-magnitude anchors; real limits depend on item size, access pattern, partition heat, quotas, and Region.
| Dimension | Number | Why it matters |
|---|---|---|
| Latency | Single-digit ms (sub-ms with DAX) | Consistent regardless of table size — the headline feature |
| Scale | Automatic table growth, quota-governed | No manual sharding; table/account quotas and hot keys still matter |
| Item size | Max 400 KB per item | Large blobs go to S3 with a pointer in the item |
| Per-partition limit | ~3,000 RCU / 1,000 WCU before adaptive help | Adaptive capacity/split-for-heat help, but one very hot key still has a ceiling |
| Consistency | MREC eventual cross-Region; MRSC strong across exactly three Regions | Strong local reads are not the same as global strong consistency |
| Capacity model | On-demand default; provisioned for steady high utilisation | On-demand is cheaper than it used to be; check current pricing |
Capabilities in interviews
Key-value & item lookups
Get or put a single item by primary key in single-digit milliseconds at any scale.
The bread-and-butter pattern. A URL shortener stores { short_code (PK), long_url, created_at } and every redirect is one GetItem on short_code — sub-10ms, whether you have a thousand or ten billion links:
PutItem { PK: "abc123", long_url: "https://…" }
GetItem { PK: "abc123" } → one partition, one hop, ~5msNo index tuning, no sharding, no capacity ceiling to plan. This is exactly where DynamoDB shines and a relational DB would need read replicas and a cache to match.
Choose this variant when
- Lookups by a known id (short codes, session ids, user ids)
- Idempotency-key storage
- Anything that is fundamentally a hash-map at scale
Query by partition + sort key
Fetch a ranged, ordered slice of items that share a partition key in one call.
With a composite key, items under one partition key are ordered by sort key, so "latest N for this user" is one Query:
PK = user#42, SK = ts#<timestamp>
Query(PK="user#42", SK begins/between …, ScanIndexForward=false, Limit=20)This serves feeds, message threads, time-series per device, and order history — any "items belonging to X, ordered by time/rank" pattern. Pagination uses the last evaluated key as a cursor. Model the sort key to encode the ordering and ranges you need.
Choose this variant when
- Feeds / timelines keyed by user
- Message or event history per entity
- Time-series per device or per tenant
Secondary access patterns (GSI)
Add a Global Secondary Index to query the same items by a different key.
When you need a second way in — look up a user by email when the table is keyed by user_id — you add a GSI on email. DynamoDB maintains it asynchronously:
Base: PK = user_id
GSI: PK = email → Query(email = "a@b.com")Budget for it: a GSI is a separate copy with its own capacity, it is eventually consistent, and over-indexing multiplies write cost. Add indexes for real access patterns, not "just in case."
Choose this variant when
- A second known query path on the same data
- Looking items up by an alternate attribute
- Sparse indexes (only items with the attribute are indexed)
Change streams & event-driven reactions
DynamoDB Streams emit every item change for downstream processing.
Every write can emit a change record to a DynamoDB Stream, which a Lambda or consumer reads to trigger side effects — update a search index, fan out a notification, maintain an aggregate, or process MREC global-table changes:
write item → Stream → Lambda → (index in Elasticsearch / send notification / update counter)This turns the database into an event source without a separate CDC pipeline, and is how you keep derived stores in sync with a DynamoDB system of record.
Choose this variant when
- Reacting to writes (search indexing, notifications, aggregates)
- Cross-region replication via Global Tables
- Outbox-style event emission from the data layer
Operating knobs
Partition key design
The single most important decision. Pick a high-cardinality key that spreads writes evenly and matches your dominant query. DynamoDB adaptive capacity and split-for-heat reduce many hot-partition problems, but a single very hot item is still bounded by partition limits. For naturally skewed keys (a celebrity, a hot tenant), write-shard by appending a suffix (user#42#3) and scatter-read across the suffixes.
On-demand vs provisioned capacity
On-demand bills per request and absorbs unknown or bursty load automatically, and after the 2024 price cuts it is the default answer for most new tables. You can also set a maximum on-demand throughput per table/GSI to bound cost or protect downstream systems; requests beyond that target are throttled. Provisioned (RCU/WCU with auto scaling) still wins for steady, predictable, high-utilisation traffic where you can keep capacity hot. Check current pricing rather than memorising an old rule.
Warm throughput for known spikes
On-demand is not magic for a brand-new table that jumps from zero to a giant flash-sale peak. DynamoDB exposes warm throughput for tables and GSIs: the read/write rate the resource is ready to support instantly. For a known launch, Prime-Day-like sale, or migration, check the warm values, account/table quotas, and pre-warm if the expected spike is 10×, 100×, or more above recent history.
Single-table vs multi-table design
Advanced DynamoDB packs multiple entity types into one table with overloaded, generic keys (PK/SK) so related items co-locate and one query returns a whole object graph. It is powerful and minimises round-trips, but it is harder to evolve. For interviews, knowing it exists and why (co-location, fewer requests) is usually enough.
Consistency choice
Eventually consistent reads are the default — cheaper and lower latency, fine for most reads. Request strongly consistent reads only where you must read your own write immediately within a region; they cost more capacity and do not work on GSIs. For global tables, name the mode: MREC is multi-active with last-writer-wins and possible cross-Region staleness; MREC transactions are only atomic in the Region where they run. MRSC is multi-Region strong consistency for stricter cases, but it is available only in supported Regions, uses exactly three Regions (or two plus a witness), and does not support DynamoDB transaction operations.
Versus the alternatives
DynamoDB vs the alternatives.
| Dimension | DynamoDB | PostgreSQL | Cassandra |
|---|---|---|---|
| Model | Managed key-value / document | Relational, rich SQL | Self-managed wide-column |
| Scaling | Automatic, no servers to operate (per-partition and account quotas apply) | Vertical + replicas; sharding is manual | Linear by adding nodes (you operate it) |
| Queries | Key-based; pre-designed access patterns | Ad-hoc joins, aggregations, filters | Key-based, partition-aware |
| Ops burden | None (fully managed) | Moderate (you run it / RDS) | High (you run the cluster) |
| Best for | Known key-access at scale, ops-light | Default for relational + transactions | Write-heavy at scale, multi-region, self-hosted |
Failure modes & gotchas
A low-cardinality or skewed partition key concentrates traffic on one internal partition. DynamoDB adaptive capacity and split-for-heat can isolate hot items, but a single hot key can still hit the ~3,000 RCU / 1,000 WCU partition ceiling, and table/account quotas still apply. Choose a high-cardinality key; write-shard known-hot keys with a suffix and scatter-gather on read.
If you model the table like SQL and later need a query the keys do not support, your only option is a full-table Scan — slow and expensive at scale. Enumerate the access patterns first and design keys (and GSIs) for each; a Scan in production is a design smell.
GSIs are maintained asynchronously and are always eventually consistent — a read of a GSI right after a write may miss it. Do not put a GSI on a read-your-write-critical path; use the base table's strongly-consistent read for that.
Items max out at 400 KB. Large payloads (images, documents, big blobs) belong in S3 with just a pointer stored in DynamoDB. Trying to stuff large values into items leads to throttling and cost blowups.
Every GSI is another copy that every write must update, multiplying write capacity consumption. Add indexes only for access patterns you actually serve; "just in case" indexes silently inflate cost.
In production
Amazon
Prime Day 2025 on DynamoDB — tens of trillions of calls
DynamoDB is the database behind high-traffic Amazon properties including Amazon.com, Alexa, and fulfillment systems, and Prime Day is its signature stress test. AWS's Prime Day 2025 post says that over the course of Prime Day these sources made tens of trillions of calls to the DynamoDB API, and that DynamoDB maintained high availability with single-digit-millisecond responses while peaking at 151 million requests per second.
This is the literal embodiment of the interview pitch: the access patterns are known and key-based (cart by customer, item by id), the scale is enormous and extremely spiky, and the team operates zero sharding infrastructure. The important 2026 nuance: if you are planning your own Prime-Day-like event, on-demand helps with unknown spikes, but you still check warm throughput, table/index quotas, and hot keys, and pre-warm known extreme peaks.
Global streaming watchlists (illustrative)
Per-user streaming state with global tables
AWS's media-and-entertainment DynamoDB guidance names Disney+ for content metadata and customer events used to generate personalized recommendations, and describes watchlist, bookmarking and global-table patterns more generally (naming Max and Prime Video for watchlists). The access pattern is textbook key access: partition by user_id, query a user's items by sort key, and keep per-user state close to viewers across Regions.
What made DynamoDB the right call was not "NoSQL because SQL is bad"; it was the shape of the workload: user-scoped reads/writes, global low latency, and operationally light replication. For classic global tables (MREC), strongly consistent reads are only latest for writes made in the same Region; cross-Region convergence is eventual and conflicts use last-writer-wins. If a streaming workflow truly needs globally current reads, that is where MRSC enters the conversation.
Good vs bad answer
Interviewer probe
“For a URL shortener doing 10B redirects/month, what database stores the code → URL mapping?”
Weak answer
"A relational database like MySQL with the short code as the primary key. If reads get heavy I'll add read replicas and a cache in front."
Strong answer
"DynamoDB. The access pattern is dead simple and key-based — GetItem by short_code — and the scale is large and read-heavy, which is exactly DynamoDB's sweet spot: single-digit-millisecond lookups at any size with no sharding to operate. Table is { short_code (PK), long_url, created_at, owner }; every redirect is one hash lookup on a well-distributed key (the codes are random, so no hot partition). I'd use on-demand capacity as the default for launch spikes, check warm throughput and quotas before a known huge launch, and put DAX or a CDN in front for the hottest codes to shave latency further. I would not reach for SQL here — there are no joins, no ad-hoc queries, nothing relational; I'd just be signing up to run replicas and shard a system DynamoDB already manages. If we later needed 'all links by owner', that's a GSI on owner, accepting its eventual consistency."
Why it wins: Matches the engine to the access pattern (key-based, read-heavy, huge scale), names the key and why it distributes, picks the capacity mode with a reason, anticipates the second access pattern as a GSI with its caveat, and explicitly rejects SQL for the right reason rather than dogma.
Interview playbook
When it comes up
- Massive scale with simple, known, key-based access patterns
- Write-heavy or spiky workloads where you do not want to operate sharding
- Sessions, carts, feeds, idempotency keys, user profiles at scale
- The interviewer asks "SQL or NoSQL?" and the queries are genuinely key-based
Order of reveal
- 11. Justify by access pattern. The queries here are key-based and high-scale, so I model access patterns and use DynamoDB — not because I am avoiding SQL.
- 22. Name the keys. Partition key is X (high-cardinality, matches the dominant query); sort key encodes the ordering/ranges I need.
- 33. Call hot-partition risk. If a key can get hot, I write-shard it with a suffix and scatter-read; otherwise the key distributes evenly.
- 44. Capacity + consistency. On-demand capacity for spiky load; warm-throughput check for known extreme events; eventual reads by default, strong only where I read my own write.
- 55. Extra patterns via GSI / Streams. A second query path is a GSI (eventually consistent); reactions to writes go through DynamoDB Streams.
Signature phrases
- “In DynamoDB you model access patterns, not entities.” — The defining mindset shift from SQL.
- “The partition key must be high-cardinality and match the dominant query.” — Shows you know where hot partitions come from.
- “Single-digit-millisecond latency at any scale, zero sharding to operate.” — Names the actual reason to pick it.
- “A GSI is a separate, eventually-consistent copy — not a free index.” — Demonstrates you understand the cost.
Likely follow-ups
?“How do you handle a celebrity / hot key?”Reveal
First, know that adaptive capacity and split-for-heat can help with uneven traffic, but they do not make one item infinitely writable. For a predictable hot key, write-shard it: append a small random or bucketed suffix to the partition key (user#42#0 … user#42#9) so writes spread across partitions. On read you scatter-gather and merge. For hot reads, front the item with DAX or an application cache so most reads never hit the partition. The goal is to keep any single key/partition under the ~3,000 RCU / 1,000 WCU ceiling and under table/account quotas.
?“You need a query the keys do not support. Options?”Reveal
Add a GSI on the attribute you want to query, which re-partitions the items under that new key — accepting that it is eventually consistent and consumes its own write capacity. If it is a one-off analytical query rather than an access pattern, export to S3 and query with Athena, or stream changes to a system built for ad-hoc queries. What I avoid is a production Scan, which reads the whole table. If I find myself wanting many ad-hoc queries, that is a signal the workload may not be a DynamoDB fit at all.
?“Strong vs eventual consistency here — which and why?”Reveal
Default to eventually consistent reads: cheaper, lower latency, and fine for the vast majority of reads where a few hundred milliseconds of staleness is invisible. Use strongly consistent reads only on the specific path where a user must immediately read their own write within the region (e.g. read-back right after an update). Note strong reads cost more capacity and are not available on GSIs, so I keep them targeted rather than blanket-on.
Worked example
Setup. Design the storage for a shopping cart service: hundreds of millions of users, massive spiky traffic (think a flash sale), every operation is "get/put this user's cart" or "add an item." Single-digit-millisecond latency, and it must not fall over under a 10× spike.
The move. This is the DynamoDB sweet spot — the access pattern is purely key-based and the scale is large and spiky. Table carts with partition key = `user_id`; the whole cart is one item (a list of line items as a document), or, if carts can be large, partition key user_id + sort key item_id so each item is its own row and "add item" is a single PutItem. Either way every operation is one hash lookup on a high-cardinality key (user ids distribute evenly — no hot partition), so latency is ~5 ms regardless of table size.
Capacity + consistency. I use on-demand capacity as the default for a spiky cart workload, but I still check warm throughput, table/index quotas, and hot-key risk before a known flash sale. If the expected peak is far above recent history, I pre-warm the table or GSI instead of assuming a brand-new on-demand table can jump infinitely. Reads are eventually consistent by default (a cart read a few hundred ms stale is invisible), and I request a strongly consistent read only on the read-back right after checkout where the user must see their final cart.
The 400 KB rule + large carts. An item caps at 400 KB. A normal cart is tiny, but to be safe against a pathological cart I use the user_id + item_id row-per-item model so no single item grows unbounded, and "get cart" becomes a Query on the partition.
Reacting to writes. Cart abandonment emails and inventory holds are driven off DynamoDB Streams → Lambda, so the data layer emits change events without a separate CDC pipeline.
What breaks. The risk is a future query the keys don't support — "all carts containing product X" for a recall. That is a Scan (slow/expensive), so I'd add a GSI keyed on product_id (eventually consistent, its own capacity) rather than scan. And I keep an eye on any write-sharding need only if a single user could somehow get hot, which carts don't.
The result. Sub-10 ms cart operations, automatic scaling for normal uncertainty, explicit pre-warm planning for known extreme peaks, no sharding or servers to operate, and change-driven side effects via Streams — the workload DynamoDB is purpose-built for.
Cheat sheet
- •Managed key-value / document store: single-digit-ms latency at very large scale, no sharding to operate (per-partition limits still apply).
- •Model access patterns, not entities. Enumerate queries first, design keys for each.
- •Partition key = hash placement + must be high-cardinality. Sort key = order/range within it.
- •Hot key ceiling ~3K RCU / 1K WCU before adaptive help — write-shard skewed keys, cache hot reads (DAX).
- •GSI = separate eventually-consistent copy on a new key, own capacity. Add for real patterns only.
- •Capacity: on-demand is the 2026 default for most new/spiky tables; provisioned+autoscale for steady high utilisation.
- •Known huge event? Check warm throughput, table/index quotas, and pre-warm rather than hoping.
- •Reads eventual by default; strong on request (in-region, not on GSIs); MREC global tables are eventual cross-Region, MRSC is stricter but constrained.
- •Item ≤ 400 KB → large blobs in S3 with a pointer. Streams for change-driven side effects.
Drills
Why does DynamoDB give constant latency whether the table is 1 GB or 1 PB?Reveal
Because every access goes through the partition key hash to exactly one internal partition, and DynamoDB transparently splits partitions as data and throughput grow. A lookup is always "hash the key, go to one partition, read the item" — that work does not increase with table size. The trade is that you only get this for key-based access; anything requiring a scan or a non-key filter loses the guarantee.
Interviewer: "design the keys for a chat app's messages."Reveal
Partition key = conversation_id so all messages in a conversation co-locate; sort key = timestamp (or a monotonic message id) so they are ordered. Then "latest 50 messages in a conversation" is one Query(PK=conversation_id, ScanIndexForward=false, Limit=50) with the last key as the pagination cursor. If you also need "all conversations for a user," that is a separate access pattern — a GSI keyed by user_id, or a separate item type in a single-table design.
When is DynamoDB the wrong choice?Reveal
When the access patterns are unknown or change frequently (you cannot pre-design keys), when you need ad-hoc queries, joins, or aggregations, or when you need rich multi-row transactions across a relational model. Those are Postgres strengths. DynamoDB is also overkill at tiny scale where a single Postgres would do everything with more flexibility. The decision is about the shape of the queries, not "SQL bad, NoSQL good."
What it is