Engineering Atlas › System Design Interviews

DynamoDB for system design interviews

13 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

Keys and items

An item is a set of attributes up to 400 KB. The primary key is a partition key, optionally plus a sort key. Items sharing a partition key form an item collection, stored together and sorted by sort key, so Query can read a range (all orders for a customer between two dates) at up to 1 MB per page. Scan reads everything and is rarely the right answer.

Capacity

1 WCU is one write of up to 1 KB; 1 RCU is one strongly consistent read of up to 4 KB, or two eventually consistent ones. Provisioned mode reserves units per second, optionally auto scaled; on-demand, now the default and recommended mode, bills per request and absorbs up to double the previous peak instantly. A new on-demand table starts able to sustain 4,000 writes and 12,000 reads per second, and going past double the previous peak within 30 minutes can throttle, so pre-warm before a known spike (AWS docs). Every table also has a default quota of 40,000 read and 40,000 write units per second, adjustable on request (AWS docs).

Indexes, transactions, streams

A global secondary index (GSI) is a second table with a different key, maintained asynchronously. A local secondary index (LSI) re-sorts within the same partition key, must be created with the table, and caps each item collection at 10 GB. Transactions cover up to 100 items at double cost. Streams emit an ordered per-item change log retained 24 hours. Global tables replicate across regions, active-active. The default mode (multi-Region eventual consistency) replicates asynchronously, typically within a second, and resolves concurrent writes per item by last-writer-wins. You can instead create a table with multi-Region strong consistency: exactly three Regions (or two plus a witness), writes synchronously replicated before they return, strongly consistent reads anywhere and a recovery point of zero, at the cost of cross-Region write latency and no transactions, TTL or LSIs (AWS docs).

Down the stack

Layer 1The contract

A successful PutItem is durable in at least two of three availability zones before you get a 200: the leader acknowledges once a quorum of replicas has persisted the log record (DynamoDB paper, USENIX ATC 2022). A strongly consistent GetItem reflects every write acknowledged before it started. An eventually consistent read, the default, may miss a recent write; the docs promise no bound, only that a repeated read soon returns the newer item, and in practice the lag is usually far under a second. Reads are read-committed per item: you never see a write that did not succeed (AWS docs). Conditional writes (ConditionExpression) are atomic per item, which is how you build optimistic locking, idempotency keys and uniqueness. GSIs and streams are always eventually consistent; strongly consistent reads work only on the table and its LSIs. When you exceed capacity on a partition or table you get ProvisionedThroughputExceededException or ThrottlingException, which SDKs retry with backoff. Typical latency is 2 to 10 ms at p50 inside a region, with p99 under 20 ms for small items (hedge: varies with item size and region).

Layer 2The mechanism

Request routing. Every request lands on a fleet of stateless request routers. A router authenticates, looks up the partition map from a metadata service (cached heavily), hashes the partition key to find the partition, and forwards to that partition's leader. Routers also enforce global admission control: token buckets that track table-level capacity across the router fleet.

Partitions as replication groups. Each partition has three replicas in different zones. The replicas run Multi-Paxos: one leader, chosen by election, holds a lease. Writes go to the leader, which assigns a log sequence number, appends to its write-ahead log, and sends the entry to its peers; once a quorum (two of three, counting itself) has persisted the entry, it applies the write to its B-tree and acknowledges. Strongly consistent reads must go to the leader; eventually consistent reads can go to any replica. If the leader's lease lapses, a follower that sees no heartbeats starts an election, and the new leader serves no writes or strong reads until the old leader's lease has expired, so two leaders never both answer (DynamoDB paper).

Storage nodes. Each replica stores data in a B-tree keyed by the hash of the partition key plus sort key, alongside its replication log. Logs are archived to S3 continuously, which is what powers point-in-time recovery. When a storage node fails, the system first adds a log replica, a member that holds only recent log entries, which takes seconds to create; full B-tree replicas are rebuilt in the background. This keeps the write quorum healthy without waiting minutes to copy gigabytes.

Splits. A partition splits when it exceeds its size limit or when sustained traffic is concentrated on it. A split divides the hash range (or, for heat, picks a split point by observed key distribution) and the new partitions get their own capacity. A single partition key's items cannot be split by hash, which is why one key is ultimately bounded by one partition's 1,000 WCU and 3,000 RCU. DynamoDB also declines to split for heat when the traffic is on one item or walks the key range sequentially, since a split would not spread it, and splits take minutes rather than seconds (DynamoDB paper).

Adaptive and burst capacity. Early DynamoDB split provisioned throughput evenly across partitions, so a hot partition throttled while others idled. Burst capacity banks up to 300 seconds of unused throughput. Adaptive capacity lets a hot partition exceed its share, up to the per-partition maximum, while the table stays under its total, and split-for-heat then isolates the hottest ranges, down to a partition holding a single item. It will not split item collections when the table has an LSI (AWS docs). Internally the paper describes this as global admission control: each request router keeps a local token bucket and refills it from a central service every few seconds.

Layer 3The machine

A small GetItem crosses the network twice inside the region: client to router (often over a pooled HTTPS connection; TLS handshake costs are why connection reuse matters), and router to storage node. Each hop is sub-millisecond within a zone and about 0.5 to 1 ms across zones. A write adds a quorum round trip from the leader to a peer in another zone, plus a log append, typically to SSD where an fsync-equivalent costs tens to hundreds of microseconds. That adds up to the familiar 3 to 6 ms write.

Items are serialised in an internal binary format; attribute names count toward item size and capacity, so a 300-byte payload with long attribute names can tip a write from 1 WCU to 2 WCU. Reads are charged per 4 KB block, rounded up, and a Query sums the sizes of all items returned before rounding, which is why batching small items in one collection is cheaper than many GetItem calls.

Why it's built this way

  1. Multi-tenant predictability was the goal, so every partition has a hard ceiling and every table has admission control; one tenant cannot starve others on a shared storage node.
  2. Hard ceilings make skew painful, so adaptive capacity, burst credits and split-for-heat were added to let real, uneven workloads use the capacity they paid for.
  3. Zones fail independently, so three replicas across three zones with a two-of-three quorum survive a whole-zone loss without losing acknowledged writes.
  4. Copying a full replica takes minutes, so log replicas restore the quorum in seconds while the B-tree copy catches up.
  5. Hashing the partition key spreads load but destroys order across keys, so ordering exists only within one item collection, and global queries need GSIs or another system.

Diagram

sequenceDiagram
    participant C as Client SDK
    participant R as Request router
    participant M as Partition metadata
    participant L as Leader (AZ-a)
    participant F1 as Follower (AZ-b)
    participant F2 as Follower (AZ-c)
    C->>R: PutItem(pk, item)
    R->>R: auth + admission token bucket
    R->>M: hash(pk) -> partition (cached)
    R->>L: write
    L->>L: assign LSN, append WAL
    L->>F1: replicate entry
    L->>F2: replicate entry
    F1-->>L: ack
    L->>L: quorum 2/3, apply to B-tree
    L-->>R: OK
    R-->>C: 200

Worked example

A ticketing service stores seat holds: 30,000 writes per second at a big on-sale, 600-byte items, and 90,000 strongly consistent reads per second. That is 30,000 WCU and 90,000 RCU, so at least 30 partitions.

The first design keys on event_id with seat_id as sort key. For one concert every write hits one partition, capped at 1,000 WCU, so 29,000 writes per second throttle whatever the table's provisioning. Adaptive capacity cannot exceed the per-partition ceiling for one key.

The fix is write sharding: key event_id#shard with shard = hash(seat_id) mod 50, spreading a concert over 50 keys and up to 50,000 WCU. "All available seats" becomes 50 parallel Query calls, about 5 to 10 ms. Holds use a conditional write (attribute_not_exists(holder) OR expires_at < :now) so two buyers cannot hold one seat. At $0.625 per million on-demand writes in us-east-1 (AWS pricing), a two-hour peak costs about $135, far less than provisioning 30,000 WCU all month. Two traps remain: a fresh on-demand table sustains only 4,000 writes per second, so pre-warm it (warm throughput) to the on-sale peak beforehand, and request a quota increase if any table or index needs more than 40,000 units.

In the wild

Trade-offs

OptionStrengthsWeaknessesChoose when
DynamoDBNo ops, predictable latency at any scale, per-item conditional writesAccess patterns fixed up front, 400 KB items, hot-key ceilings, AWS lock-inKnown key-based access at large or spiky scale
CassandraOpen source, multi-cloud, huge write throughput, tunable consistencyYou run it: repairs, compaction, upgradesWrite-heavy time series on your own infrastructure
Aurora or PostgresSQL, joins, ad hoc queries, rich transactionsWrite scaling needs sharding; connection limitsRelational data, moderate scale, evolving queries
MongoDBFlexible documents, secondary indexes, aggregationSharding and operations are on you unless using AtlasDocument data with varied query needs

Pitfalls in production

Leadership lens

Check yourself

  1. A table is provisioned with 10,000 WCU and is throttling at 1,200 WCU of total traffic. How?

    Show answer

    The traffic is concentrated on one partition key, and one partition cannot exceed about 1,000 WCU regardless of table capacity.

  2. Why are eventually consistent reads half the price of strongly consistent ones?

    Show answer

    Any of the three replicas can serve them, while strongly consistent reads must go to the leader, so the system can spread them more cheaply.

  3. What does a log replica buy during a node failure?

    Show answer

    It restores the two-of-three write quorum in seconds by holding only recent log entries, instead of waiting minutes to copy a full B-tree replica.

  4. Why can a GSI slow down writes to the base table?

    Show answer

    Each base write must also be propagated to the GSI; if the GSI lacks capacity, DynamoDB throttles base writes to keep the index from falling too far behind.

Sources and further study

Primary

Systems docs

Books

Other curricula

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →