Singleton Items in DynamoDB
A singleton item is a single row with a fixed, hardcoded key that holds state for your whole application — not one record per user or per order, but one record, period. Feature flags, a config blob, a global kill-switch: the kind of thing a relational app would keep in a one-row settings table.
Coming from SQL, you'd reach for a config table with id = 1 and a SELECT * FROM config. In DynamoDB you do the same thing with a hardcoded partition
key — and because you always know that key, you read it with a GetItem, not
a Queryor aScan.
What is a singleton item in DynamoDB?
A singleton item is a single DynamoDB row stored under a fixed, hardcoded key that holds global state for your whole application — feature flags, a config blob, a system-wide version — rather than one record per user or order. Because you always know the key, you read it with a GetItem and update it with plus condition expressions.
- A singleton is one item with a constant key. You hardcode the
PK/SKin your code (e.g.CONFIG#GLOBAL) instead of templating in a user or order id. - Read it with
GetItem, neverScan. You always know the full key, so a point read is one consistent, predictable RCU — no filter, no table walk. - It's a by definition. Every request can touch the same partition, so cache it and keep the item small; don't make it a write bottleneck.
- Mutate it safely with update + condition expressions, not read-modify-write in your app — that's where the lost-update race lives.
Recognize the pattern
You have global state when the data isn't scoped to any one entity. A few tells:
- A flag that's the same for everyone (
signup_enabled = false). - A blob of tunables your app reads on boot (rate limits, default quotas).
- A counter or version number for the whole system, not per-row.
Anything scoped to a user, tenant, or order is not a singleton — that's an ordinary item keyed by that entity's id. The singleton is the leftover global slice that has nowhere else to live.
Give it a constant key
The whole pattern hinges on one decision: the key is a literal, not a template. For a global feature-flags item in an overloaded single table, pick a fixed prefix and a fixed value:
| PK | SK | attributes |
|---|---|---|
| SETTINGS#APP | FLAGS#V1 | signup_enabled, maintenance_mode, ai_search_enabled |
PK = "SETTINGS#APP" and SK = "FLAGS#V1" are baked into the code. There's no
user id, no tenant id — the application asks for exactly this item every time.
That predictability is the point: a known key is a GetItem, and a GetItem
is the cheapest, most consistent read DynamoDB offers.
The V1 suffix is deliberate. If the flag schema changes shape later, you write
a FLAGS#V2 item and flip readers over, instead of mutating the live one in
place. Versioning the singleton key buys you a clean migration seam.
Read it with GetItem
Because the key is fully known, you never Query and you never Scan for a
singleton. A Scan reads the whole table and filters client-side — the classic
Scan footgun — and it's absurd overkill for fetching
one row you can address directly.
A GetItem against SETTINGS#APP / FLAGS#V1 returns the flags in a single
strongly- or eventually-consistent read. AWS bills a GetItem of an item ≤ 4 KB
as 0.5 RCU eventually-consistent or 1 RCU strongly-consistent
(AWS read/write capacity docs).
Keep the singleton small and that cost stays flat forever.
The read path is just: app boots or a request lands, you GetItem the fixed
key, you cache the result. Here's the flow.
The fixed key turns a global lookup into one point read with a built-in default path.
Note the no branch: a missing singleton should never crash you. Default to the
safe value (feature off, maintenance on) so a first-deploy gap or a bad
key fails closed, not open.
Update it without a race
The trap is updating a singleton with read-modify-write in your app: you
GetItem the flags, flip one in memory, then PutItem the whole thing back.
Two concurrent writers both read the old item and the second Put clobbers the
first's change. Lost update.
Two DynamoDB features kill the race without app-side locking:
- mutate one attribute server-side, leaving the rest
untouched. No need to re-
Putthe whole item. - make the write succeed only if the item still looks
the way you expect, so a stale write is rejected with
ConditionalCheckFailedException(AWS condition expression docs).
To flip one flag, target just that attribute with a SET and guard it with a
version bump so concurrent writers can't trample each other:
# UpdateItem
Key PK=SETTINGS#APP SK=FLAGS#V1
UpdateExpression SET signup_enabled = :on, schema_version = :next
ConditionExpression schema_version = :current
If two writers race, the second one's schema_version = :current check fails
and it retries against the fresh value. You can scaffold the names, values, and
this exact expression shape in the
DynamoDB Expression Builder before wiring
it into code. For a deeper look at the operators, see the
update-expression idioms guide.
Mind the hot key
A singleton is, by construction, a hot key — every part of your app may read the same partition. That's fine for reads if you cache, but it's the one real risk of the pattern.
- Cache aggressively. Read the flags once per process (or per N seconds), not on every request. The singleton's value is the cheapest thing to memoize.
- Don't make it a write hot spot. A flag toggled by an admin a few times a day is nothing. A singleton you increment on every request is a partition-throughput bottleneck — that's a counter problem, not a singleton.
- Keep it small. Read cost scales with item size in 4 KB blocks. A bloated config blob makes every boot more expensive than it needs to be.
If you genuinely need a high-write global counter, the singleton is the wrong shape — shard it across N items and sum on read. That's a different pattern.
Singleton vs per-entity item
The line is simply what the data is scoped to.
| Singleton item | Per-entity item | |
|---|---|---|
| Key | Hardcoded constant (SETTINGS#APP) | Templated with an id (USER#42) |
| How many | Exactly one | One per user / order / tenant |
| Typical read | GetItem on the known key | GetItem or Query by entity |
| Scope | Whole application | A single entity |
| Use for | Global flags, config, system version | Profiles, orders, anything per-id |
If you find yourself wanting two singletons of the same kind, you don't have a singleton — you have a per-entity item and the entity is the thing you forgot to key by (per-tenant config, say).
Pitfalls and next steps
- Don't
Scanfor it. You know the key; address it directly. - Don't read-modify-write it. Use update + condition expressions.
- Don't let it go missing silently. Default to the safe value on a cache miss.
- Don't overload it with high-frequency writes. That's a sharded-counter job.
The singleton lives comfortably inside a single-table design — it's just one more item collection with a fixed key alongside your entity rows.
Try DynoTable to browse your table, find the singleton row by its fixed key, and edit flags by hand while you build the write path.