String.prototype.charCodeAt()

The original character-code method, and a 16-bit one. For anything outside the Basic Multilingual Plane it returns half a character, which is why codePointAt was added.

String methodES1 (1997)Live demo
Common call
s.charCodeAt(0)
Returns
a number 0–65535, or NaN
Replaces
nothing; the inverse is String.fromCharCode
Watch out
out of range is NaN, and NaN !== NaN
string.charCodeAt(indexindex — Position of the code unit. Negative or beyond the end gives NaN rather than throwing. It does NOT count from the end.type: number · default: 0)
→ number

Demo

Live evaluation
Try:
Inputs
sstringthe source string
indexnumberindex
Output
'ABC'.charCodeAt(0)
65

Capital A is 65 and lowercase a is 97, a difference of 32 — the bit that old case-conversion tricks flipped. The digit 0 is 48, which is why subtracting 48 converts a digit character to its value. The out-of-range case returns NaN, not undefined and not an error, and NaN is not equal to itself — so a check like charCodeAt(i) === NaN is always false. Use Number.isNaN, or use codePointAt, which returns undefined instead.

Parameters

NameTypeRequiredDescription
indexnumberno (0)Position of the code unit. Negative or beyond the end gives NaN rather than throwing. It does NOT count from the end.

Return value

number — An integer from 0 to 65535 — the UTF-16 code unit at that index. NaN if the index is out of range, which is the trap.

Common patterns

Digit character to value
The classic offset trick.
const value = ch.charCodeAt(0) - '0'.charCodeAt(0);
Simple hash over a string
Where the code unit is just a number to mix.
let h = 0;
for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0;
Prefer codePointAt for real characters
Handles astral characters correctly.
const cp = s.codePointAt(0);

Examples

1. Capital A
'A'.charCodeAt(0)
Returns
65
2. Out of range
'abc'.charCodeAt(99)
Returns
NaN
3. codePointAt differs
'abc'.codePointAt(99)
Returns
undefined
4. Half an emoji
'\u{1F600}'.charCodeAt(0)
Returns
55357
5. The whole one
'\u{1F600}'.codePointAt(0)
Returns
128512
6. Emoji length
'\u{1F600}'.length
Returns
2

Pitfalls

1. Out of range is NaN, and NaN breaks comparisons
Every comparison against NaN is false, including equality with itself, so an out-of-range read propagates silently through arithmetic rather than failing loudly. A hash loop that runs one index too far quietly produces NaN for every subsequent step.
Never true
'abc'.charCodeAt(99) === NaN
false
Test properly
Number.isNaN('abc'.charCodeAt(99))
true
2. It returns half an astral character
An emoji occupies two code units, and charCodeAt gives you one of them — a lone surrogate in the 0xD800–0xDFFF range that means nothing on its own. codePointAt reads the pair and returns the real code point.
A surrogate half
'\u{1F600}'.charCodeAt(0)
55357
The code point
'\u{1F600}'.codePointAt(0)
128512
3. Negative indices do not count from the end
Unlike at and slice, a negative index here is simply out of range and gives NaN. There is no negative-index form of this method.
NaN
'abc'.charCodeAt(-1)
NaN
Use at first
'abc'.at(-1).charCodeAt(0)
99
4. Case conversion by arithmetic only works for ASCII
Adding or subtracting 32 flips case for the 26 unaccented Latin letters and corrupts everything else. toLowerCase and toUpperCase know the Unicode rules; arithmetic does not.
Wrong for accents
String.fromCharCode('É'.charCodeAt(0) + 32)
'é' only by luck — fails for most scripts
Use the method
'É'.toLowerCase()
'é'

When to use

Use it
  • Hashing or checksums, where a 16-bit unit is all you need
  • ASCII arithmetic — digit values, simple ranges
  • Interoperating with APIs that work in UTF-16 code units
Reach for something else
  • Text that may contain emoji or other astral characters → codePointAt
  • You want the character, not its number → at or charAt
  • Case conversion → toLowerCase and toUpperCase
  • Comparing strings → localeCompare

Notes

Complexity
O(1)
Return
A number, or NaN; nothing is allocated
CPython impl
V8: Builtins-string-charcodeat
Memory
No allocation
Thread-safe
Single-threaded; the string is only read

FAQ

codePointAt for anything involving real text — it handles surrogate pairs and returns undefined rather than NaN when out of range. charCodeAt when you genuinely want UTF-16 code units, which is mostly hashing and low-level encoding work.

'\u{1F600}'.charCodeAt(0);   // 55357 — half
'\u{1F600}'.codePointAt(0);  // 128512 — whole

History

ES1
charCodeAt present from the first version, when strings were assumed to be UCS-2.
ES2015
codePointAt and String.fromCodePoint added to handle characters beyond the BMP.