DynamoDB Data Types: A Complete Reference
Every DynamoDB data type — scalars (S, N, B, BOOL, NULL), documents (M, L) and sets (SS, NS, BS) — how they're stored, sorted, and when to use each.
On this page
DynamoDB has a small but precise set of attribute types, and knowing exactly how each is stored, sorted, and constrained will save you from subtle bugs — empty-set rejections, lexicographic number sorting, and oversized items among them.
This guide is a complete reference to every type, grouped into scalars, documents, and sets.
The type system at a glance
Every attribute value in DynamoDB is tagged with a one- or two-letter type descriptor. You see these directly when working with the low-level API or BatchWriteItem JSON:
| Category | Type | Descriptor | Holds |
|---|---|---|---|
| Scalar | String | S | UTF-8 text |
| Scalar | Number | N | up to 38 digits of precision |
| Scalar | Binary | B | raw bytes (base64 on the wire) |
| Scalar | Boolean | BOOL | true / false |
| Scalar | Null | NULL | a typed null marker |
| Document | Map | M | nested key-value object |
| Document | List | L | ordered, mixed-type array |
| Set | String Set | SS | unique strings |
| Set | Number Set | NS | unique numbers |
| Set | Binary Set | BS | unique byte values |
High-level SDKs (like boto3’s resource interface or the AWS SDK Document clients) hide these descriptors and map them to native language types, but the underlying storage rules still apply.
Scalar types
String (S)
UTF-8 encoded text. Strings can be empty ("") as non-key attributes, but a key attribute cannot be empty. Maximum length is bounded only by the 400 KB item size limit.
Strings sort lexicographically by UTF-8 byte value, which is why they’re so useful as sort keys when formatted carefully:
- ISO-8601 timestamps (
2026-06-16T09:30:00Z) sort chronologically. - Zero-padded numbers (
"00042") sort numerically. - Mixed-case sorts by byte: uppercase
A(0x41) precedes lowercasea(0x61).
Number (N)
A single numeric type covering integers and decimals, with up to 38 digits of precision. There is no separate int/float/decimal — N is variable-length and decimal-based, so you avoid floating-point rounding. On the wire it’s transmitted as a string to preserve precision:
{ "price": { "N": "19.99" }, "quantity": { "N": "3" } }
Numbers sort numerically in keys and indexes. Use N for anything you’ll do arithmetic on or range-query as a true number. Note that values are normalized: trailing zeros after the decimal are trimmed (1.50 stored as 1.5).
Binary (B)
Arbitrary bytes — compressed blobs, encrypted payloads, thumbnails. You supply base64-encoded data over the wire; DynamoDB stores the raw bytes and counts the decoded size against the 400 KB limit. Binary values sort by unsigned byte order.
Boolean (BOOL)
A simple true / false. Stored compactly; can be used in expressions with comparisons and as a filter.
Null (NULL)
A typed marker meaning “this attribute exists and is explicitly null,” distinct from the attribute simply being absent. In most data models you should omit an attribute rather than store NULL, because absent attributes are free and sparse — and sparse attributes enable sparse-index patterns. Reach for NULL only when “explicitly empty” is semantically different from “missing.”
Document types
Document types let you nest structure inside a single item. They can be arbitrarily deep (up to a 32-level nesting limit), and the entire item still must fit within 400 KB.
Map (M)
An unordered collection of key-value pairs — essentially a nested object. Values can be any type, including other maps and lists.
{
"address": { "M": {
"city": { "S": "Lisbon" },
"zip": { "S": "1100-001" },
"geo": { "M": { "lat": { "N": "38.72" }, "lng": { "N": "-9.13" } } }
}}
}
You can read and update nested attributes directly with document paths in expressions, e.g. SET address.city = :c, without rewriting the whole map. See expressions for path syntax.
List (L)
An ordered array that can hold mixed types and duplicates:
{ "tags": { "L": [ { "S": "urgent" }, { "N": "5" }, { "BOOL": true } ] } }
You can append to a list atomically with list_append in an UpdateExpression, and address elements by index (tags[0]). Lists preserve order, which sets do not.
Set types
Sets hold unique values of a single scalar type. They’re unordered and enforce uniqueness automatically.
| Type | Descriptor | Element type |
|---|---|---|
| String Set | SS | strings |
| Number Set | NS | numbers |
| Binary Set | BS | binary |
{ "roles": { "SS": ["admin", "editor", "viewer"] } }
The big advantage of sets is atomic membership operations. ADD inserts elements (no-op if already present) and DELETE removes them, both atomically and idempotently — no read-modify-write race:
table.update_item(
Key={"userId": "U#1"},
UpdateExpression="ADD roles :r",
ExpressionAttributeValues={":r": {"newrole"}},
)
Critical constraints:
- Sets cannot be empty. DynamoDB rejects an empty set; if a
DELETEwould empty it, the attribute is removed entirely. - Single type only. You can’t mix strings and numbers in one set — use a List for that.
- No ordering. Don’t rely on element order; use a List if order matters.
Set vs List: choosing correctly
| List (L) | Set (SS/NS/BS) | |
|---|---|---|
| Order | preserved | none |
| Duplicates | allowed | rejected |
| Mixed types | yes | no |
| Atomic add/remove | append only | ADD / DELETE |
| Can be empty | yes | no |
Use a Set for tags, roles, or unique IDs where you do membership math. Use a List for ordered sequences, mixed-type collections, or anything that can contain repeats.
Types and primary keys
Key attributes (partition and sort keys) must be scalar — only S, N, or B are allowed. You cannot key on a Boolean, set, document type, or null. This is covered in detail in primary keys, but it’s worth remembering when you design an item: the attributes you query by must be plain scalars.
Item size and the 400 KB ceiling
The total size of an item — all attribute names plus all values, including nested structure — must stay under 400 KB. Names count too, so terse attribute names save real bytes at scale. For large blobs, store the object in S3 and keep only a pointer (a Binary or String reference) in DynamoDB.
Inspecting types in real data
Because high-level SDKs hide the type descriptors, it’s easy to lose track of whether a field is stored as N or S — a difference that silently breaks sort order and range queries. Tablyne, a native DynamoDB GUI, shows each attribute’s exact type, renders nested maps and lists as a tree, and flags items approaching the 400 KB limit, which makes type mismatches visible before they bite a query.
Master these types and you’ll model data that’s compact, correctly sorted, and cheap to query — and you’ll never be surprised by an empty-set error again.
Frequently asked questions
Why does DynamoDB store numbers as strings?
Internally DynamoDB stores Number (N) values as variable-length, sign-and-magnitude decimal, and the low-level wire format represents them as strings to preserve up to 38 digits of precision without floating-point rounding errors.
Can a DynamoDB set be empty?
No. Sets (SS, NS, BS) cannot be empty — DynamoDB rejects an empty set. If a set would become empty, remove the attribute instead. Empty strings and empty lists/maps, however, are allowed.
What's the difference between a List and a Set in DynamoDB?
A List (L) is ordered, can hold mixed types, and allows duplicates. A Set (SS/NS/BS) is unordered, must hold a single type, and enforces uniqueness. Use a Set when you need uniqueness and atomic ADD/DELETE operations.