charCodeAt()
ES3+Returns the numeric UTF-16 code unit at the given index, an integer between 0 and 65535, or NaN when the index is out of range. For characters encoded as surrogate pairs, such as emoji, it returns one half of the pair; codePointAt() returns the full code point instead. It is the numeric counterpart of charAt().
Syntax
string.charCodeAt(index)Parameters
index number The index of the character
Return Value
A number representing the UTF-16 code unit
Examples
const str = 'ABC';
console.log(str.charCodeAt(0));
console.log(str.charCodeAt(1)); 📌 When to Use
Use charCodeAt() when you need to compute with characters rather than display them: classifying characters as letters, digits, or symbols by their numeric ranges, implementing ciphers and hash functions, comparing or sorting by raw code order, validating that input stays within ASCII, or converting between letters and alphabet positions. Character arithmetic is its home turf: shifting a letter by N positions, mapping the letter A to index 0 by subtracting 65, or checking whether a code falls in the 48-to-57 digit range are all one-line numeric operations that would be awkward with string comparison. It pairs with String.fromCharCode(), which performs the reverse mapping from numbers back to characters, and together they form the classic toolkit for algorithm exercises and low-level text processing. Know its boundary: it reads UTF-16 code units, so any character above U+FFFF, including every emoji, appears as two surrogate values in the 0xD800 to 0xDFFF range, and naive per-index processing will see those halves instead of the character. When full Unicode matters, use codePointAt() with for-of iteration and String.fromCodePoint(). For simple equality with a known character, direct string comparison or includes() is clearer; reserve charCodeAt() for genuinely numeric logic.
⚠️ Common Mistakes
Reading surrogate halves as characters: for an emoji, charCodeAt(i) returns a value between 55296 and 57343 (a surrogate), and charCodeAt(i + 1) the other half. Range checks and ciphers written for BMP text silently corrupt astral characters; use codePointAt() when input may contain them.
Forgetting NaN for out-of-range indices: charCodeAt(str.length) is NaN, and NaN propagates through arithmetic and fails every comparison, so an off-by-one in a scanning loop produces confusing downstream values rather than an exception.
Hard-coding magic numbers without comment: 65, 90, 97, 122, and 48 to 57 are the uppercase, lowercase, and digit ranges, but a reader should not need to know that by heart. Name them as constants or derive them with 'A'.charCodeAt(0) style expressions.
Assuming the ASCII ranges cover all letters: the letter checks for A-Z and a-z classify accented letters, Greek, Cyrillic, and CJK as 'special characters'. For Unicode-aware classification, use regex property escapes like Unicode letter classes instead of raw ranges.
Mixing up charCodeAt() with charAt() in comparisons: comparing charAt(i) against a number coerces the one-character string and produces accidental results; numeric logic must read numeric codes.
Round-tripping astral code points through String.fromCharCode(): it truncates values above 65535, so code built on the charCodeAt/fromCharCode pair breaks for emoji; the fromCodePoint/codePointAt pair is the safe modern equivalent.
✅ Best Practices
Derive range constants from characters, as in const A = 'A'.charCodeAt(0), so the intent is legible and typo-proof compared with bare integers scattered through the logic.
Decide the Unicode policy up front: if input can contain non-ASCII text, either iterate code points with for-of and codePointAt() or explicitly document that the function is ASCII-only and validate that.
Use charCodeAt() in hot scanning loops instead of charAt(), comparing integers rather than allocating a one-character string per step.
Pair conversions symmetrically: charCodeAt() with String.fromCharCode() for BMP-only logic, codePointAt() with String.fromCodePoint() when astral characters are possible; never mix the two families.
For character classification in modern code, prefer regex character classes and Unicode property escapes, which express 'is a letter or digit' declaratively; keep numeric ranges for genuinely numeric algorithms like ciphers and hashes.
Validate indices before reading when correctness depends on it, since the NaN result for bad indices poisons arithmetic silently rather than failing fast.
⚡ Performance Notes
charCodeAt() is about as cheap as a JavaScript operation gets: a bounds check and a sixteen-bit read, returning a small integer with no allocation. JIT compilers inline it aggressively, and the charCodeAt-in-a-for-loop pattern is the standard high-performance way to scan strings, precisely because it avoids the per-character string allocation that charAt() or bracket indexing incur. Hash functions, parsers, and validators over large inputs should read numeric codes and only materialize strings for results. codePointAt() costs marginally more, since it may inspect two code units and can return values needing more than sixteen bits, but the difference is trivial next to its correctness benefit for astral characters. When building output from computed codes, batch them: collecting codes in an array and calling String.fromCharCode once with spread, or joining chunks, beats appending one character per iteration to an accumulator string.
🌍 Real World Example
Character Encoding and Simple Cryptography
Three numeric-character algorithms. caesarCipher() reads each code, detects the uppercase and lowercase ranges, applies the shift with modular arithmetic so Z wraps to A, and rebuilds characters with String.fromCharCode, passing everything else through untouched, the classic introductory cipher done properly. isAscii() scans for any code above 127, the standard quick test that a string is plain ASCII before applying byte-oriented processing. analyzeString() tallies letters, digits, and other characters by range checks, the shape of logic inside password-strength meters and input validators. All three show the same core move: convert to numbers, reason arithmetically, convert back only at the end.
// Simple Caesar cipher encryption
function caesarCipher(text, shift) {
let result = '';
for (let i = 0; i < text.length; i++) {
const code = text.charCodeAt(i);
// Uppercase letters (A-Z: 65-90)
if (code >= 65 && code <= 90) {
result += String.fromCharCode(((code - 65 + shift) % 26) + 65);
}
// Lowercase letters (a-z: 97-122)
else if (code >= 97 && code <= 122) {
result += String.fromCharCode(((code - 97 + shift) % 26) + 97);
}
else {
result += text.charAt(i);
}
}
return result;
}
console.log(caesarCipher('Hello World', 3)); // 'Khoor Zruog'
// Check if string contains only ASCII characters
function isAscii(str) {
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 127) {
return false;
}
}
return true;
}
console.log(isAscii('Hello')); // true
console.log(isAscii('Hello')); // true (Korean characters)
// Count letters, digits, and special characters
function analyzeString(str) {
let letters = 0, digits = 0, special = 0;
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122)) {
letters++;
} else if (code >= 48 && code <= 57) {
digits++;
} else {
special++;
}
}
return { letters, digits, special };
}
console.log(analyzeString('Hello123!')); // { letters: 5, digits: 3, special: 1 }