DynamoDB
Query
Case-Insensitive
Database
AWS

Query DynamoDB with case-insensitive condition

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

DynamoDB does not provide a built-in case-insensitive query operator for string attributes. String comparisons are effectively case-sensitive, so the practical solution is to normalize the data at write time and query a normalized attribute rather than trying to make the query engine fold letter case during reads.

Why the Query Cannot Do This for You

A DynamoDB Query is designed around exact key access patterns. It can use the partition key and, optionally, sort-key operators such as equality, prefix matching, or ranges. None of those operators introduces case folding for strings.

A filter expression does not solve the problem either. Filters run after matching items are read, and the string comparison behavior still remains case-sensitive. So this is not a case-insensitive search feature hidden behind a different API.

Normalize the Attribute at Write Time

The usual design is to store a second attribute in a canonical form such as lowercase.

python
1import boto3
2
3client = boto3.resource("dynamodb")
4table = client.Table("Users")
5
6email = "[email protected]"
7
8item = {
9    "pk": f"USER#{email.lower()}",
10    "email": email,
11    "email_normalized": email.lower(),
12}
13
14table.put_item(Item=item)

Now every write preserves the original display value and also stores a predictable lookup value.

Query the Normalized Key or Index

If the normalized field is part of the primary key design, query directly by that key. If not, create a global secondary index that uses the normalized attribute.

python
1response = table.query(
2    KeyConditionExpression=boto3.dynamodb.conditions.Key("pk").eq("USER#[email protected]")
3)
4
5print(response["Items"])

If you use a GSI, the same rule applies: query the index on the normalized attribute, not the original mixed-case string.

Example with a Secondary Index Pattern

Suppose the table stores customer records by internal ID, but you also need case-insensitive lookup by username. A reasonable design is:

  • base table primary key: customer_id
  • GSI partition key: username_normalized

Write path:

python
1item = {
2    "customer_id": "C123",
3    "username": "AliceSmith",
4    "username_normalized": "alicesmith",
5}

Read path:

python
1response = table.query(
2    IndexName="username-normalized-index",
3    KeyConditionExpression=boto3.dynamodb.conditions.Key("username_normalized").eq("alicesmith")
4)

That is fast, predictable, and compatible with DynamoDB's intended access model.

Do Not Reach for Scan First

A common reaction is to run a Scan, lowercase values in application code, and filter manually. That technically works on small tables, but it is almost always the wrong design for production because:

  • scans touch far more data than necessary
  • latency grows with table size
  • read capacity usage becomes wasteful
  • pagination and throttling become more painful

If the access pattern matters, model it into the keys.

Think About Locale and Normalization Rules

Case-insensitive matching is rarely just lower(). If identifiers are emails, ASCII usernames, or slugs, simple lowercase normalization is usually fine. If the field is natural-language text, locale and Unicode normalization rules can get more complicated.

That means you should define the matching contract clearly. For many application identifiers, the contract is simply "store and query lowercase ASCII or lowercase Unicode strings using one consistent normalization rule".

Common Pitfalls

The most common mistake is trying to bolt case-insensitive behavior onto a filter expression. That does not change how DynamoDB compares strings, and it still happens after items are read.

Another mistake is normalizing only the query value but not the stored attribute. Both sides must follow the same rule.

Teams also often add a normalized attribute but forget to index it, which leaves them forced back into scans.

Finally, do not use original mixed-case values as the key if your business rule says lookups should ignore case. Encode the business rule into the data model.

Summary

  • DynamoDB does not support native case-insensitive string query semantics.
  • The practical solution is to store a normalized attribute at write time.
  • Query the normalized attribute directly or through a GSI.
  • Avoid scans for this problem unless the dataset is genuinely tiny.
  • Define one normalization rule and apply it consistently on writes and reads.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.