String.prototype.codePointAt()
codePointAt(index) The codePointAt method returns a non-negative integer representing the Unicode code point at the given index. Unlike charCodeAt, it correctly handles surrogate pairs—characters outside the Basic Multilingual Plane (BMP) that require two 16-bit units in JavaScript strings.
Syntax
codePointAt(index)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| index | number | — | The position in the string. Returns undefined if index is out of bounds or negative. |
The method returns the code point at the given index, or undefined if the index points beyond the string. The examples below walk through basic usage with ASCII and emoji characters, then show how to compare codePointAt with charCodeAt and convert code points back to characters.
Examples
Basic usage
const str = "Hello, 🌍";
console.log(str.codePointAt(0)); // 72 (H)
console.log(str.codePointAt(1)); // 101 (e)
console.log(str.codePointAt(5)); // 32 (space)
console.log(str.codePointAt(7)); // 128751 (🌍)
The basic example shows how codePointAt works with a mix of ASCII and emoji characters. Notice that the emoji at index 7 returns a value well above 65535, which is the ceiling for characters that charCodeAt can represent in a single 16-bit unit. The next example compares both methods directly with an emoji to make that gap visible, and also shows how the spread operator iterates by code point.
Handling surrogate pairs
const emoji = "😀";
console.log(emoji.codePointAt(0)); // 128512
console.log(emoji.charCodeAt(0)); // 55357 (incorrect - returns high surrogate)
// Spread operator (iterates by code points)
[..."A😀Z"].forEach(cp => console.log(cp.codePointAt(0)));
// 65
// 128512
// 90
Once you have a code point, you may want to turn it back into a character. String.fromCodePoint() is the inverse of codePointAt() — it accepts one or more code point values and builds the corresponding string. This is especially useful after you have analysed or modified individual code points and need to reassemble them.
Converting code points back to characters
const codePoint = 128512; // 😀
console.log(String.fromCodePoint(codePoint)); // 😀
// Process each code point
const str = "Hello 🌍";
const codePoints = [...str].map(char => char.codePointAt(0));
console.log(codePoints); // [72, 101, 108, 108, 111, 32, 128751]
The examples above cover the core mechanics: calling codePointAt, comparing it with charCodeAt, and converting code points back to characters. The patterns below show how these building blocks fit into real functions that you are likely to write when processing text that contains emoji or other supplementary characters.
Common Patterns
Iterate over code points safely
function getCodePoints(str) {
return [...str].map(char => char.codePointAt(0));
}
console.log(getCodePoints("abc")); // [97, 98, 99]
console.log(getCodePoints("a🌀c")); // [97, 128300, 99]
Iterating by code point lets you inspect every character cleanly, without worrying about where surrogate pairs start and end. Sometimes you only need to know whether a string contains any characters outside the BMP at all — a quick check rather than a full iteration.
Check if a string contains surrogate pairs
function hasSurrogates(str) {
return [...str].some(char => char.codePointAt(0) > 0xFFFF);
}
console.log(hasSurrogates("hello")); // false
console.log(hasSurrogates("hello😀")); // true
Knowing whether surrogates are present tells you which iteration strategy to pick. When you do encounter them, the gap between codePointAt and charCodeAt becomes obvious — charCodeAt reads a single code unit, so it can land in the middle of a character.
Compare with charCodeAt
const str = "A🎉";
console.log(str.charCodeAt(1)); // 55349 (high surrogate)
console.log(str.codePointAt(1)); // 128397 (correct 🎉)
// charCodeAt treats each 16-bit unit separately
// codePointAt treats the string as code points
The difference matters most when you manipulate the string rather than just reading from it. Reversing a string, for example, will break any character that crosses a surrogate pair unless you iterate by code point first. The example below shows both approaches side by side.
Process text containing emojis
function reverseString(str) {
return [...str].reverse().join("");
}
console.log(reverseString("Hello😀World")); // dlroW😀olleH
// Without spreading, reversing would break the emoji:
// "Hello😀World".split("").reverse().join("") // "dlroW口olleH" (broken)
The examples above all rely on spreading the string to work around the fact that JavaScript stores text as UTF-16 code units. That underlying representation is the reason all these workarounds exist, so it is worth understanding exactly how index-based access behaves with multi-unit characters.
Why index-based access is tricky with Unicode
JavaScript strings are sequences of UTF-16 code units, not Unicode code points. Characters with code points above U+FFFF (emoji, many CJK extension characters, mathematical symbols) are stored as two consecutive 16-bit values called a surrogate pair. When you pass an index to codePointAt(), the index is still a UTF-16 code unit index, not a code point index. This means that for a string starting with a surrogate-pair character, codePointAt(1) returns the low surrogate (a partial code point), not the next logical character.
To iterate safely by code point, spread the string or use a for...of loop — both use the string iterator, which yields one code point at a time regardless of how many code units each character requires.
const str = '😀A';
// Code unit indexing (be careful)
str.codePointAt(0); // 128512 — full emoji code point
str.codePointAt(1); // 56832 — low surrogate, partial code point
str.codePointAt(2); // 65 — 'A'
// Safe: iterate by code point
for (const char of str) {
console.log(char.codePointAt(0));
}
// 128512, 65
Why this beats charCodeAt() for text work
codePointAt() is the safer choice whenever your code handles user-facing text. Emoji, symbols, and many non-Latin scripts can occupy two UTF-16 code units, so treating the string as simple 16-bit chunks can split a character in half. Using codePointAt() makes that mismatch visible. It is still an index-based API, though, so you should pair it with code-point-aware iteration when you need to walk the whole string.
Common mistake with indexes
One trap is assuming that the index passed to codePointAt() behaves like a character count. It does not. The index is still measured in UTF-16 code units, which means the index for the next logical character may skip by one or two positions depending on the text. That is why a for...of loop or spread syntax is usually the safer starting point when you want to inspect every character in a string.
codePointAt() vs charCodeAt()
charCodeAt(index) always returns a 16-bit value (0–65535). For characters above U+FFFF, it returns the individual surrogate code units rather than the full code point. codePointAt(index) recognises surrogate pairs and returns the full code point value (up to 1,114,111). Prefer codePointAt for any code that handles international text or emoji.
See Also
- String.prototype.charAt() — Returns the character code at an index (does not handle surrogates)
- String.prototype.charCodeAt() — Returns the code point at an index (handles surrogates)
- String.fromCodePoint() — Creates a string from code point values