ASCII Character Codes: The Table, How to Find a Code, and Where ASCII Lives in UTF-8

November 8, 2026

A protocol document says fields are separated by byte 0x1F. A hex editor shows 41 42 43 where you expected readable letters. A log line ends with 0D 0A and nobody remembers why there are two bytes for one newline. All three mysteries resolve the same way: every byte names a character, and the mapping between numbers and characters is the ASCII table. Reading that table in both directions is a small skill that pays off whenever you inspect files, debug protocols, write parsers or hunt down invisible characters in text that looks perfectly clean.

Why the codes run from 0 to 127

ASCII stands for American Standard Code for Information Interchange. It was standardized in the 1960s, when text traveled over telegraph and phone lines between teletype machines, and every extra bit on the wire cost real money and time. The compromise was seven bits: two to the seventh power gives exactly 128 code positions, numbered from 0 to 127. Seven bits instead of eight was deliberate, because the eighth bit position on transmission lines was reserved for a parity check, a crude error-detection mechanism.

This is where a popular myth comes from: that ASCII contains 255 or 256 characters. A byte does hold values from 0 to 255, but ASCII only describes the lower half of that range. Codes 128 to 255 were filled in later by national encodings built on top of the table, and each of them defined its own upper half: CP1251 carried Cyrillic, CP850 carried DOS box-drawing characters, ISO-8859-1 carried accented Latin letters. ASCII itself remains a seven-bit standard that stops at code 127. The history of the standard and its place today are covered in the article on what ASCII is and where it is used.

Control characters: codes 0 to 31 and 127

The first 32 codes and the last one, code 127, are not printable. They are control characters: instead of depicting a letter or a sign they command a device. They were invented for teletypes, typewriter-like machines connected by wire, and the abbreviations still carry the mechanics. CR, carriage return, is the physical movement of the printing head back to the start of the line. LF, line feed, is the rotation of the paper roller by one line.

  • LF, code 10: line feed. Every line in a Unix or macOS text file ends with it, and programming languages write it as \n;
  • CR, code 13: carriage return. The pair CR LF, bytes 13 and 10 in a row, terminates lines on Windows and in network protocols such as HTTP, SMTP and FTP;
  • TAB, code 9: horizontal tabulation, visible in text as an indentation step;
  • NUL, code 0: the null byte, the string terminator of the C language, the marker that a string has ended;
  • ESC, code 27: the start of terminal escape sequences; after it the terminal expects a command such as a color change or a cursor move;
  • BEL, code 7: the bell. On a real teletype this code actually rang a small bell to alert the operator;
  • DEL, code 127: delete. On paper tape it punched out every hole of a position, turning the character into blank tape that readers skipped.

Most of the 32 control codes are unused today, but they could not be removed from the table: the format was fixed by the standard, and decades of equipment depended on exact numbers. A handful are still everywhere. LF, CR, TAB, ESC and NUL appear in nearly every text format and protocol. The rest survive as a historical layer, occasionally resurfacing in printer drivers, old terminal emulators and modem command sets.

The printable range 32 to 126

Everything that can be printed on paper lives between codes 32 and 126 inclusive: 95 characters. You do not need to memorize the whole table. Five anchor points are enough to reconstruct everything around them.

  • the space is code 32, the very first printable character: an invisible-looking mark with a full-fledged number of its own;
  • digits 0 to 9 occupy codes 48 to 57, consecutive with no gaps;
  • uppercase Latin letters A to Z occupy codes 65 to 90;
  • lowercase Latin letters a to z occupy codes 97 to 122;
  • the tilde ~ is code 126, the last printable character of the table.

Punctuation, brackets and math signs sit between the digits, the uppercase block and the lowercase block, so codes run without gaps inside each group, with separators like the colon and the at sign between groups. This is where a classic interview question comes from, listing the code ranges for digits and letters, and the whole answer is three pairs of numbers: 48 to 57, 65 to 90 and 97 to 122.

There is a pleasant side effect when reading hex dumps: the low nibble of a digit code matches the digit itself. The character 7 has code 55, written in hexadecimal as 0x37, and the trailing 7 repeats the value. Digits are therefore instantly recognizable inside dumps: the range 0x30 to 0x39 is always digits. Any code can be converted between hex and decimal in the base converter.

How to find the code of a character

For a one-off question the fastest route is a ready-made table. In the ASCII table tool you type a character and get its code in decimal, hexadecimal and binary at once, and the reverse lookup shows the character behind a given number, which is convenient for decoding bytes from a dump. To see a whole string as bytes, the binary translator renders each character of the line in its bit representation.

When lookups happen many times a day, one line of code beats any table. In Python the ord() function returns the code of a character and chr() converts a code back:

Character codes in Python
>>> ord('A')
65
>>> ord('a')
97
>>> ord('0')
48
>>> ord(' ')
32
>>> chr(97)
'a'
>>> hex(ord('A'))
'0x41'

In JavaScript, strings expose charCodeAt(), which returns the code of a character by index, and codePointAt(), which handles characters beyond the basic plane of Unicode. Across the entire ASCII range both methods agree; the difference only appears above code 65535.

Character codes in JavaScript
'A'.charCodeAt(0)   // 65
'a'.charCodeAt(0)   // 97
'0'.charCodeAt(0)   // 48
'\u042F'.charCodeAt(0)   // 1071: Cyrillic lies outside ASCII
'\u042F'.codePointAt(0)  // 1071

The distinction between the methods matters for large characters such as emoji. charCodeAt() returns a UTF-16 code unit, so for those characters it hands back half of a surrogate pair, a number like 55357, while codePointAt() returns the real Unicode code point, for example 128512. For Latin letters, digits and Cyrillic both methods behave identically, because all of them fit into a single code unit.

Why lowercase letters sit exactly 32 above uppercase

Uppercase A has code 65, lowercase a has code 97, and the difference is exactly 32. This is not a coincidence but an engineering decision by the standards committee: the number 32 is the weight of the fifth bit of a byte, so a case change is a single bit flip. In binary, 65 is 1000001 and 97 is 1100001: only the bit with weight 32 differs. That simplification mattered for teletype-era electronics, and it still works inside every modern programming language.

Several practical consequences follow. Converting a Latin letter to uppercase means subtracting 32 from its code, and testing whether a letter is lowercase means asking whether its code lies between 97 and 122. In hexadecimal the pairs are easy to spot: 0x41 and 0x61, 0x5A and 0x7A, exactly one digit of the notation changes. Sorting has a side effect too: when strings are ordered by raw byte values, every uppercase letter lands before every lowercase letter, because 65 is smaller than 97. That is why Zebra sorts before apple in byte order, and why real sorting algorithms treat case separately instead of trusting raw codes.

The same trick explains the digits: the code of the character 5 minus the code of zero gives the number five, 53 minus 48. String-to-number conversion functions perform exactly this subtraction internally, which is why a hand-written digit parser fits in three lines and needs no lookup table.

ASCII inside UTF-8: the first 128 bytes match

UTF-8 encodes characters in a variable number of bytes, from one to four. The splitting rule is simple. Characters with codes 0 to 127 are written as a single byte whose value equals the code itself. Everything above takes two, three or four bytes, and each of those bytes starts with the bits 10 or 11, which makes them instantly distinguishable from plain ASCII bytes.

The consequence is important: any file containing only ASCII characters looks byte-for-byte identical in UTF-8 and in pure ASCII. English text opens without loss in any encoding of the UTF family, and old software built around English letters and digits keeps working with UTF-8 content as long as it stays within Latin script. The moment Cyrillic or emoji appears, the paths diverge: the uppercase Russian letter Ya carries Unicode code 1071 and takes two bytes, D0 AF, instead of one.

That compatibility is precisely why UTF-8 won the web: it breaks no legacy format while the text is English, yet it can express every written language on the planet. The full walkthrough of UTF-8, multi-byte sequences and the BOM lives in the article about UTF-8 and Unicode.

Checking that a file is pure ASCII

Why check files for ASCII purity at all? The reasons are practical. Configuration formats such as legacy .ini files, and many protocols, expect nothing but English letters, digits and punctuation, and a single Cyrillic letter inside a comment can crash the parser. Key files and PEM certificates must also be byte-for-byte ASCII: one invisible character from another encoding breaks parsing so that the error surfaces as an invalid key format while the file looks flawless on screen. Logs consumed by automation are safer in ASCII too.

The check reduces to a single condition: every byte of the file must be smaller than 128. In Python that is two lines.

Checking a file for pure ASCII
data = open('config.ini', 'rb').read()
print(all(b < 128 for b in data))
# True: the file is pure ASCII
# False: bytes from another encoding or from UTF-8 are present

# where exactly it breaks:
bad = [i for i, b in enumerate(data) if b > 127]
print(bad[:10])

When the check returns False, the list of byte positions shows where: the first byte at 128 or above points at the first non-English character. Invisible marks get caught by the same filter. The non-breaking space carries Unicode code 160 and occupies two bytes, C2 A0, in UTF-8, and a BOM prepends three bytes EF BB BF to the file. The document looks perfect in an editor while the check reveals foreign bytes inside.

When a text mixes several encodings and the byte check keeps failing, the encoding detector names the likely encoding from the bytes themselves, which is usually the fastest way to stop guessing between CP1252, KOI8-R and UTF-8 variants.

Common mistakes

  • confusing decimal and hexadecimal notation. People see 0x41 in a dump, look up code 41 in the table and find a closing bracket. Check the prefix first: 0x41 is 65, uppercase A;
  • believing ASCII holds 255 or 256 characters. The standard defines 128 codes, of which 95 are printable, and the upper half of the byte belongs to national encodings;
  • forgetting that the space is a character. It has a real code, 32, and it counts toward string length just like any letter;
  • attributing Cyrillic to ASCII. There is no code for the Russian letter A in the table: the number 192 is empty in ASCII, while in CP1251 it already means Cyrillic A;
  • using charCodeAt() on non-ASCII characters and being surprised by numbers like 1071. That is not a bug: Cyrillic genuinely lives beyond the first 128 codes;
  • expecting the letter yo to sit with the other Cyrillic letters. Even inside CP1251 the letter yo breaks out of the orderly block and sits apart from the rest.

Frequently asked questions

What are the codes of the letters A and a?

Uppercase A has code 65 and lowercase a has code 97. The whole family is remembered as three ranges: digits 48 to 57, uppercase 65 to 90, lowercase 97 to 122. The case difference is always exactly 32, so lowercase follows from uppercase by simple addition, and back by subtraction.

How do ASCII and Unicode differ?

ASCII is a table of 128 characters with codes from 0 to 127. Unicode is far wider: it defines more than 150 thousand characters, and its codes are written in the form U+number, for example U+0410 for the Russian letter A. UTF-8 is the byte-level way of writing Unicode codes, and the first 128 Unicode characters in that encoding match ASCII byte for byte, which is exactly why the compatibility survived.

How do I find the code of a character that is not in ASCII?

For Cyrillic, emoji and everything else you need the Unicode code. In Python the same ord() function returns it: ord() applied to the Russian Ya gives 1071, written U+042F. In JavaScript use codePointAt() rather than charCodeAt() for such characters, so you get the full code instead of half a surrogate pair.

Why do Windows files contain 13 and 10 for one newline?

That is the CR LF pair, a teletype inheritance: the carriage first returned to the start of the line, then the paper advanced by one line. Windows adopted the pair wholesale, Unix and macOS use only LF, and classic Mac OS before X used only CR. Network protocols such as HTTP require the CR LF pair regardless of the operating system on either end.

What is extended ASCII?

The phrase refers to the family of eight-bit encodings that filled codes 128 to 255 with extra symbols: box drawing in CP866 and CP850, Cyrillic in CP1251 and KOI8-R, accented Latin letters in ISO-8859-1. No single extended ASCII table exists: every vendor and region defined its own upper half, which is why a file saved in one such encoding reads wrongly in another.

Memorize five anchor codes: space 32, zero 48, A 65, a 97, DEL 127. The rest of the table rebuilds itself from them: digits step by one from zero, letters run in alphabetical order. In hexadecimal the check takes seconds: 0x41 and 0x61 are the A and a pair, 0x30 is zero, and any byte above 0x7F is already outside ASCII.

Related articles