character encoding
utf8
latin1
text encoding differences
encoding comparison

Differences between utf8 and latin1

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

UTF-8 is a variable-width encoding that supports every Unicode character using 1 to 4 bytes per character. Latin-1 (ISO-8859-1) is a fixed-width single-byte encoding that supports only 256 characters, covering ASCII and Western European languages. The key practical difference is that UTF-8 can represent any language on earth while Latin-1 cannot, but Latin-1 uses less storage for Western European text and has simpler string indexing since every character is exactly one byte.

For any new project, UTF-8 is the correct default. Latin-1 still matters when you work with legacy systems, older databases, or data pipelines that were built before Unicode became standard.

How Each Encoding Works

UTF-8 Byte Sequences

UTF-8 encodes characters using a variable number of bytes. The first byte's leading bits indicate how many bytes the character uses.

text
1U+0041  A         -> 0x41              (1 byte,  ASCII range)
2U+00E9  e with accent -> 0xC3 0xA9    (2 bytes, Latin Extended)
3U+4E16  Chinese char  -> 0xE4 0xB8 0x96 (3 bytes, CJK)
4U+1F600 Emoji         -> 0xF0 0x9F 0x98 0x80 (4 bytes)

Characters in the ASCII range (U+0000 to U+007F) use exactly one byte, identical to ASCII. This backward compatibility is one of the reasons UTF-8 became the dominant encoding on the web.

Latin-1 Byte Values

Latin-1 maps byte values 0x00 through 0xFF directly to the first 256 Unicode code points. Every character is exactly one byte.

text
1U+0041  A              -> 0x41 (1 byte)
2U+00E9  e with accent  -> 0xE9 (1 byte)
3U+00A9  copyright sign -> 0xA9 (1 byte)
4U+4E16  Chinese char   -> cannot be represented

This direct byte-to-character mapping makes Latin-1 simple to work with programmatically. String length equals byte length, and random access by character index is O(1).

Side-by-Side Comparison

FeatureUTF-8Latin-1 (ISO-8859-1)
Bytes per character1 to 4Always 1
Total characters supportedOver 1.1 million (full Unicode)256
ASCII compatibleYes (first 128 bytes identical)Yes (first 128 bytes identical)
Western European languagesYesYes
CJK, Arabic, Hebrew, etc.YesNo
Emoji supportYesNo
String indexingO(n) for character indexO(1) for character index
Storage for English textSame as ASCII (1 byte/char)Same as ASCII (1 byte/char)
Storage for European accented text2 bytes per accented character1 byte per accented character
Web adoptionOver 98% of websitesLegacy use only

Storage and Performance Implications

The storage difference depends entirely on the content. For ASCII-only text (English without special characters), UTF-8 and Latin-1 use the exact same number of bytes. For text heavy in accented characters (French, German, Spanish), UTF-8 uses roughly 10-20% more space because accented characters become two bytes instead of one.

python
1text = "Renee fait du cafe"  # ASCII-only French
2print(len(text.encode('utf-8')))    # 18 bytes
3print(len(text.encode('latin-1')))  # 18 bytes
4
5text = "Renee fait du cafe"  # with accents: Renee fait du cafe
6accented = "Renée fait du café"
7text_precomposed = "Renée fait du café"
8print(len(text_precomposed.encode('utf-8')))    # 20 bytes
9print(len(text_precomposed.encode('latin-1')))  # 18 bytes

For CJK text, UTF-8 uses 3 bytes per character, which makes it roughly three times the size of a hypothetical single-byte encoding. But since Latin-1 cannot represent CJK at all, this comparison only matters when choosing between UTF-8 and other multi-byte encodings like UTF-16.

Database Considerations

MySQL

MySQL has three relevant character sets: latin1, utf8 (which supports only up to 3-byte characters, missing emoji and some CJK), and utf8mb4 (full 4-byte UTF-8).

sql
1-- Check current character set for a table
2SHOW CREATE TABLE users;
3
4-- Convert a table from latin1 to utf8mb4
5ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
6
7-- Set default for new tables
8ALTER DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

The MySQL utf8 type is a common trap. It only supports characters up to 3 bytes, which means it silently truncates emoji and some CJK characters. Always use utf8mb4 for true UTF-8 support.

PostgreSQL

PostgreSQL uses UTF-8 by default and does not have the 3-byte limitation. Latin-1 (LATIN1) is available as a database encoding but is rarely used in modern deployments.

sql
1-- Check database encoding
2SHOW server_encoding;
3
4-- Typical output for modern PostgreSQL
5-- UTF8

Detecting and Converting Encodings

Python

python
1# Detect encoding with chardet
2import chardet
3
4with open("data.txt", "rb") as f:
5    raw = f.read()
6    result = chardet.detect(raw)
7    print(result)  # {'encoding': 'ISO-8859-1', 'confidence': 0.73}
8
9# Convert Latin-1 to UTF-8
10text = raw.decode('latin-1')
11utf8_bytes = text.encode('utf-8')

Command Line

bash
1# Check file encoding
2file -bi data.txt
3# Output: text/plain; charset=iso-8859-1
4
5# Convert with iconv
6iconv -f ISO-8859-1 -t UTF-8 data.txt > data_utf8.txt

Java

java
1import java.nio.charset.StandardCharsets;
2
3// Convert Latin-1 bytes to UTF-8 string
4byte[] latin1Bytes = readFromLegacySystem();
5String text = new String(latin1Bytes, StandardCharsets.ISO_8859_1);
6byte[] utf8Bytes = text.getBytes(StandardCharsets.UTF_8);

Web and HTTP Headers

Browsers and servers communicate encoding expectations through HTTP headers and HTML meta tags.

html
1<!-- HTML5 (UTF-8 is the recommended default) -->
2<meta charset="UTF-8">
3
4<!-- Older HTML with Latin-1 -->
5<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

The HTTP Content-Type header can also specify encoding:

text
Content-Type: text/html; charset=utf-8

As of 2024, over 98% of websites use UTF-8. The W3C and WHATWG both recommend UTF-8 as the default encoding for all new content.

Common Pitfalls

Treating Latin-1 bytes as UTF-8 without conversion. Latin-1 bytes in the range 0x80 to 0xFF are invalid as standalone UTF-8 bytes. Reading a Latin-1 file as UTF-8 produces decoding errors or mojibake (garbled text like "cafe" instead of "cafe").

Using MySQL utf8 instead of utf8mb4. MySQL's utf8 encoding only supports 3-byte characters. Emoji, musical notation, and some CJK characters will be silently truncated or rejected. Always use utf8mb4 for proper UTF-8 support.

Assuming string length equals byte length in UTF-8. In Latin-1, len(bytes) == len(characters) always holds. In UTF-8, a 10-character string might be anywhere from 10 to 40 bytes. Code that allocates buffers based on character count will underallocate for non-ASCII UTF-8 text.

Double-encoding UTF-8. This happens when UTF-8 text is incorrectly treated as Latin-1 and then re-encoded to UTF-8. The result is byte sequences like 0xC3 0x83 0xC2 0xA9 for what should be a single accented character. If you see strings like "Renée" instead of "Renee", double-encoding is the likely cause.

Forgetting the BOM (Byte Order Mark). Some Windows tools prepend a UTF-8 BOM (0xEF 0xBB 0xBF) to files. This invisible prefix can break parsers, shell scripts, and CSV readers that do not expect it. Latin-1 files never have a BOM.

Summary

UTF-8 is the universal encoding for modern software, supporting every Unicode character with efficient storage for ASCII-heavy content. Latin-1 is a single-byte encoding limited to 256 characters, still found in legacy databases and file formats. For new projects, always choose UTF-8 (and utf8mb4 in MySQL specifically). When working with legacy Latin-1 data, use explicit conversion tools like iconv or language-level decode/encode functions, and watch for double-encoding artifacts. The most common real-world problem is not choosing between the two encodings but accidentally mixing them in the same data pipeline.


Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions