DynamoDB Primary Keys: Partition Key & Sort Key Explained

How DynamoDB primary keys work — partition (hash) keys, sort (range) keys, composite keys, and how key choice drives performance and access patterns.

5 min read· Updated Jun 15, 2026
On this page

The primary key is the single most important design decision you make in DynamoDB. It determines how data is physically distributed, which queries are fast, and which are impossible without a secondary index or a full table scan.

This guide covers the two key types, how composite keys unlock range queries, and the rules that govern good key design.

What a primary key actually does

Every item in a DynamoDB table is uniquely identified by its primary key, and DynamoDB also uses that key to decide which physical partition stores the item. The key is doing two jobs at once: uniqueness and data placement.

DynamoDB offers two kinds of primary key:

  • Simple primary key — a single attribute, the partition key (also called the hash key).
  • Composite primary key — two attributes, a partition key plus a sort key (also called the range key).

You choose one at table creation in the KeySchema, and you cannot change it later.

The partition key (hash key)

DynamoDB runs the partition key value through an internal hash function. The hash output maps the item to a partition — an allocation of storage and throughput on the underlying nodes. All items with the same partition key value live in the same item collection on the same partition.

This has direct consequences:

  • Even distribution = even load. If every request targets the same partition key, you concentrate traffic on one partition and risk a hot partition. Pick a key with high cardinality and uniform access — user IDs, order IDs, tenant IDs.
  • GetItem needs the full key. To read a single item with GetItem you must supply the entire primary key. With a simple key that’s just the partition key; with a composite key you need both.

A simple-key table behaves like a key-value store:

TableName: Sessions
KeySchema:
  - AttributeName: sessionId   KeyType: HASH
table.get_item(Key={"sessionId": "sess-9f3a"})

The sort key (range key)

Add a sort key and the table becomes a key-value-and-document store. Items sharing a partition key are stored physically sorted by the sort key value. This ordering is what makes Query powerful.

TableName: Orders
KeySchema:
  - AttributeName: customerId   KeyType: HASH
  - AttributeName: orderDate    KeyType: RANGE

Now customerId no longer has to be unique. Many orders can share one customerId as long as each has a distinct orderDate. The pair (customerId, orderDate) must be unique.

Because items are stored in sort order, you can query efficiently:

# All of a customer's orders in 2026, newest first
table.query(
    KeyConditionExpression=Key("customerId").eq("C#123")
        & Key("orderDate").between("2026-01-01", "2026-12-31"),
    ScanIndexForward=False,
)

The sort key supports a family of KeyConditionExpression operators: =, <, <=, >, >=, between, and begins_with. The partition key only supports = — you always query one partition at a time.

Key condition operators at a glance

OperatorPartition keySort key
= (equals)requiredoptional
<, <=, >, >=noyes
betweennoyes
begins_withnoyes

The asymmetry is the whole point: you pin the partition key to land on a single partition, then slice into the sorted range with the sort key. For the full breakdown of Query versus full-table reads, see query vs scan.

Data types allowed in keys

Key attributes must be scalar: String (S), Number (N), or Binary (B). You cannot use a Boolean, set, list, or map as a key attribute. Sort order depends on the type:

  • Number sorts numerically: 9 < 10 < 100.
  • String sorts lexicographically by UTF-8 bytes: "10" < "100" < "9".
  • Binary sorts by unsigned byte values.

This is why zero-padded numeric strings ("00010") or ISO-8601 timestamps ("2026-06-16T12:00:00Z") make excellent sort keys — they sort correctly as plain strings. See data types for the complete picture of how each type is stored and compared.

Composite sort keys for hierarchical data

A sort key doesn’t have to be a single concept. A widely used trick is to concatenate values with a delimiter to model hierarchy:

PK = USER#123   SK = ORDER#2026-06-16#A1
PK = USER#123   SK = ORDER#2026-05-20#B7
PK = USER#123   SK = PROFILE

Now begins_with(SK, "ORDER#") returns just the orders, sorted by date, while SK = "PROFILE" fetches the profile. Composite sort keys are the backbone of multi-entity layouts; the technique is explored in depth in single-table design.

Avoiding hot partitions

A hot partition occurs when traffic concentrates on one partition key value, throttling requests even though the table as a whole has spare capacity. Adaptive capacity mitigates this automatically by redistributing throughput, but it can’t fix a fundamentally skewed key.

Practical defenses:

  • Choose high-cardinality partition keys. A status field with five values is a terrible partition key; a userId with millions of values is great.
  • Add a write shard suffix for extreme write hotspots: append #0#9 to the key and fan out writes, then scatter-read across the shards.
  • Don’t use timestamps or sequential IDs alone as partition keys for high-write tables — every write lands in a narrow range.

Primary key design checklist

  • Will most reads supply the full key? Then GetItem is your friend — model for it.
  • Do you need ranges, sorting, or “all items of type X under parent Y”? Use a composite key.
  • Is the partition key high-cardinality and evenly accessed?
  • Do your sort key values sort the way you expect as strings or numbers?
  • Are values that must be queried together sharing a partition key?

If you need to query by an attribute that isn’t part of the primary key, you don’t redesign the table — you add a secondary index.

Inspecting keys in practice

Key design is easier to reason about when you can see real items grouped by partition and watch how sort keys order within a collection. Tablyne, a native DynamoDB GUI, renders item collections sorted by sort key and lets you build key conditions visually, which makes spotting a skewed or mis-sorted key obvious before it becomes a production incident.

Get the key right and most of your access patterns fall into place. Get it wrong and you’ll be fighting scans, hot partitions, and migrations for the life of the table.

Frequently asked questions

Can I change a DynamoDB primary key after creating a table?

No. The key schema is fixed at table creation. To change keys you create a new table with the desired schema and migrate the data, typically with a scan-and-write job or DynamoDB Streams plus a Lambda backfill.

Does the partition key have to be unique?

Only if the table has no sort key. With a simple primary key the partition key alone identifies an item and must be unique. With a composite key, many items can share a partition key as long as each has a distinct sort key.

Can the partition key or sort key be a number?

Yes. Both can be String (S), Number (N), or Binary (B). Numbers sort numerically, strings sort lexicographically by UTF-8 bytes, which matters when you design sort keys for range queries.

Keep reading