toLowerCase()
ES3+Returns a new string with every character converted to lowercase using the Unicode default case mappings, leaving the original string unchanged. Characters that have no lowercase form, such as digits, punctuation, and unicameral scripts like Chinese or Korean, pass through untouched. For language-specific rules, use toLocaleLowerCase() instead.
Syntax
string.toLowerCase()Return Value
A new string representing the original string in lower case
Examples
const str = 'Hello World';
console.log(str.toLowerCase()); 📌 When to Use
Use toLowerCase() whenever letter case should not matter: normalizing email addresses and usernames before storing or comparing them, implementing case-insensitive search and filtering, generating URL slugs, deduplicating tags, or comparing file extensions and HTTP header values. The canonical pattern is to lowercase both sides of a comparison, as in a.toLowerCase() === b.toLowerCase(), or to lowercase once and compare many times, as when filtering a product list against a lowercased query. It applies Unicode default mappings, so it handles accented Latin letters, Greek, and Cyrillic correctly, not just ASCII. Be aware of its limits, though. It is locale-independent by design: Turkish and Azerbaijani distinguish dotted and dotless i, so lowercasing the capital I correctly for Turkish users requires toLocaleLowerCase with an explicit locale argument such as tr. And for truly robust, linguistically correct case-insensitive comparison, especially where accents should also be ignored, Intl.Collator or localeCompare with sensitivity options is the better tool. Full Unicode case folding is subtler than lowercasing, but for identifiers, codes, and ASCII-centric data, toLowerCase() is exactly right. Avoid calling it on display text where the original casing carries meaning; keep the original for display and the lowercased copy for matching.
⚠️ Common Mistakes
Expecting the string itself to change: strings are immutable, so str.toLowerCase() returns a new string and leaves str exactly as it was. Forgetting to assign the result, as in writing str.toLowerCase(); on its own line, is a very common bug that silently does nothing.
Lowercasing only one side of a comparison: query.toLowerCase() === product.name will still be case-sensitive on the right-hand side. Both operands must be normalized, and ideally at the same place in the code so one side cannot drift.
Ignoring the Turkish-i problem: in Turkish and Azerbaijani, capital I lowercases to a dotless i. The locale-independent toLowerCase() always produces the ordinary dotted i, which is wrong for Turkish text and, conversely, using toLocaleLowerCase with a Turkish locale on protocol identifiers can break comparisons that expect ASCII behavior.
Assuming length is preserved: a handful of Unicode mappings change string length, for example the capital I with dot above (U+0130) lowercases to two code points. Code that assumes s.length === s.toLowerCase().length for arbitrary input can miscalculate offsets.
Using lowercase comparison where accent-insensitive matching was actually wanted: toLowerCase() does not strip diacritics, so 'café' and 'cafe' remain different. Combine normalize with diacritic removal, or use Intl.Collator with sensitivity set to base, when accents should not matter.
✅ Best Practices
Normalize at the boundary: lowercase emails, usernames, and codes once, at the point where data enters your system, rather than sprinkling toLowerCase() calls at every comparison site.
Keep the original string for display and the lowercased version for matching; users expect to see the casing they typed, even when lookups are case-insensitive.
Hoist the lowercasing of a search query out of the filter loop so it happens once, not once per item, and lowercase stored fields ahead of time if you filter them frequently.
Use toLocaleLowerCase with an explicit locale for user-facing text in case-sensitive-locale languages such as Turkish, and plain toLowerCase() for protocol data like HTTP headers, file extensions, and language-neutral identifiers where stable ASCII behavior is required.
For linguistically correct case- or accent-insensitive comparison and sorting, prefer Intl.Collator or localeCompare with sensitivity options over the lowercase-both-sides idiom.
⚡ Performance Notes
toLowerCase() scans the whole string and returns a new one, so it is linear in string length. Engines fast-path ASCII-only text and may return the original string object when nothing needs to change, but you should still treat every call as a potential allocation. The classic waste is lowercasing inside a loop: filtering ten thousand products against a query and calling query.toLowerCase() in every iteration does ten thousand identical conversions. Hoist it out. For repeatedly searched data, consider storing a pre-lowercased shadow field, trading a little memory for skipping the conversion on every keystroke of a search box. Note that Intl.Collator-based comparison, while more correct for human text, is considerably slower than comparing two lowercased strings with ===, so for large datasets the lowercase-once-and-compare approach remains the pragmatic choice.
🌍 Real World Example
User Authentication and Search
Three normalization patterns. findUserByEmail() lowercases and trims the login input, then compares it against each stored email lowercased on the fly, so 'John.Doe@Example.com' and 'john.doe@example.com' match, which is how email lookup should behave. createSlug() chains toLowerCase() with trim() and two regex replacements to turn an article title into a clean URL slug of letters, digits, and hyphens. searchProducts() lowercases the query once, outside the filter loop, then lowercases each product name inside includes() for case-insensitive matching, demonstrating both the correct comparison idiom and the hoisting optimization.
// Case-insensitive email validation for login
function findUserByEmail(users, inputEmail) {
const normalizedInput = inputEmail.toLowerCase().trim();
return users.find(user =>
user.email.toLowerCase() === normalizedInput
);
}
const users = [
{ id: 1, email: 'John.Doe@Example.com' },
{ id: 2, email: 'JANE@example.com' }
];
console.log(findUserByEmail(users, 'john.doe@example.com'));
// Output: { id: 1, email: 'John.Doe@Example.com' }
// Create URL slug from title
function createSlug(title) {
return title
.toLowerCase()
.trim()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-');
}
console.log(createSlug('Hello World! This is My Article'));
// Output: 'hello-world-this-is-my-article'
// Case-insensitive search filter
function searchProducts(products, query) {
const lowerQuery = query.toLowerCase();
return products.filter(product =>
product.name.toLowerCase().includes(lowerQuery)
);
}