Short answer: Unicode contains many characters that render almost identically but are completely different — Cyrillic а and Latin a, the micro sign µ and Greek μ, a hyphen and a minus. They break search, sorting, code and security checks, and you cannot tell them apart by looking. Check the code point instead.
The fastest way to settle any of these: paste the character into the Symbol Identifier, which names the exact code point you’re holding.
Why Unicode is full of them
It looks like a design flaw and it isn’t. Three deliberate decisions produced almost all of it.
Different scripts, same shapes. Latin, Greek and Cyrillic share ancestry, so a Cyrillic а looks like a Latin a because it is the same letterform, inherited independently. Unicode can’t merge them: they sort differently, uppercase differently, and belong to different alphabets. A Russian word spelled with Latin letters would be wrong even though it looked right.
Different meanings, same glyph. The Greek capital sigma Σ is a letter; the summation operator ∑ is mathematical notation. Same shape, different job — and mathematics software needs to tell them apart.
Round-trip compatibility. When older encodings were absorbed into Unicode, their characters came along even where a duplicate already existed. That’s why the micro sign µ exists alongside Greek μ, and the ångström sign alongside Å. Background in what Unicode is.
The index
Letters from other scripts
| Looks like | Actually is | Code point |
|---|---|---|
| a | Cyrillic small letter a | U+0430 |
| o | Greek small letter omicron | U+03BF |
| I | Cyrillic capital letter byelorussian-ukrainian i | U+0406 |
| g | Latin small letter script g | U+0261 |
These are the dangerous ones, because whole alphabets of them exist. Typing in Russian and the Greek alphabet cover the scripts they come from.
Same shape, different meaning
| Character | Official name | Code point | Confused with |
|---|---|---|---|
| µ | Micro sign | U+00B5 | μ Greek mu, U+03BC |
| ∑ | N-ary summation | U+2211 | Σ Greek sigma, U+03A3 |
| ς | Greek small letter final sigma | U+03C2 | σ sigma, U+03C3 |
| ♾ | Permanent paper sign | U+267E | ∞ infinity, U+221E |
| ♯ | Music sharp sign | U+266F | # number sign, U+0023 |
| ∼ | Tilde operator | U+223C | ~ tilde, U+007E |
| ∕ | Division slash | U+2215 | / solidus, U+002F |
| ꞉ | Modifier letter colon | U+A789 | : colon, U+003A |
| ː | Modifier letter triangular colon | U+02D0 | : colon, U+003A |
The µ/μ pair is the one that appears most in real documents — see the mu symbol and statistics symbols. The Σ/∑ pair is covered on the sigma symbol, and ♾ on the infinity symbol.
Punctuation that isn’t the punctuation you meant
| Character | Name | Code point | Mistaken for |
|---|---|---|---|
| − | Minus sign | U+2212 | – hyphen-minus, U+002D |
| ′ | Prime | U+2032 | ‘ apostrophe, U+0027 |
| ; | Greek question mark | U+037E | ; semicolon, U+003B |
The dash family is covered in the em dash, primes in the prime symbol, the tilde in the tilde symbol, and quote substitution in straight vs curly quotes.
Normalisation will not save you
This is the assumption that costs people the most time. Unicode normalisation merges characters it considers canonically equivalent — genuinely the same character written two ways. Lookalikes are not that. They’re different characters that happen to share a shape.
Of the sixteen pairs on this page, NFKC merges exactly two: the micro sign becomes Greek mu, and the Greek question mark becomes a semicolon. Both are compatibility duplicates rather than true lookalikes. The other fourteen survive every normalisation form unchanged — Cyrillic а stays Cyrillic а forever.
So normalising your input is necessary and nowhere near sufficient. It’s the wrong tool for this job.
Where it actually bites
Domain names. This is the serious one. Registrars accept non-Latin scripts, so a domain using Cyrillic а renders identically to the Latin original. Converted to its underlying form, аpple.com is xn--pple-43d.com — a completely different address that no amount of careful reading would reveal. That’s the homograph attack, and browsers now defend against it by displaying the punycode form when a domain mixes scripts suspiciously. When your browser shows you xn-- gibberish, it isn’t broken; it’s warning you.

Code. The classic prank is replacing a semicolon with the Greek question mark U+037E, which renders identically. The code looks perfect and the parser rejects it, or worse, parses it as something unintended. Cyrillic letters in identifiers produce two variables that look like one.
Search and find-and-replace. A document mixing µ and μ, or ɡ and g, silently returns half the matches. Nothing errors; the result is just quietly incomplete.
Spreadsheets. A minus sign U+2212 pasted from a typeset document isn’t a hyphen, so the cell becomes text rather than a number, and your sum is wrong rather than broken.
Usernames and identity. Two accounts whose names render identically is an impersonation vector, which is why serious systems restrict identifiers to a single script rather than trying to blacklist characters.
Filenames. The reverse case: lookalikes are used deliberately to get around blocked characters, as in invalid filename characters. Useful, and it has its own costs.
How to detect them
Check the code point, not the shape. Paste into the Symbol Identifier. This is the only method that always works.
Watch for mixed scripts. A single word containing both Latin and Cyrillic letters is almost never legitimate. That one rule catches the majority of malicious cases, and it’s cheap to implement — bucket each character by script and flag any field that mixes them.
Use the confusables data. Unicode publishes a machine-readable list of characters that look alike, along with a “skeleton” procedure: map every character to a canonical representative, then compare skeletons. Two strings with the same skeleton look alike, whichever characters they actually contain. This is what registrars and browsers use, and it’s the right answer for any system doing identity checks.
Don’t rely on people. Visual review is exactly what lookalikes defeat. If detection matters, it has to be automatic.
In code, a script check is usually a few lines:
python
import unicodedata as u
def scripts(s):
return {u.name(c).split()[0] for c in s if c.isalpha()}
scripts("аpple") # {'CYRILLIC', 'LATIN'} — mixed, worth flagging
scripts("apple") # {'LATIN'}
That’s a rough heuristic rather than a substitute for the official confusables data, but it catches the common case immediately.
Related problems that look similar
- Invisible characters — zero-width spaces and non-breaking spaces, which don’t look like anything at all. See invisible characters.
- Characters that won’t render — empty boxes are a font gap, not a lookalike. See symbols showing as boxes.
Accessibility
Here, assistive technology is better off than you are. Screen readers announce characters by Unicode name, so Cyrillic а is read as a Cyrillic letter while Latin a is read as a Latin one — a distinction the screen never shows.
That cuts both ways. A heading padded with lookalikes reads as nonsense to a screen-reader user even though it looks fine, which is a good reason not to use them decoratively in text anyone else has to read. How screen readers pronounce symbols covers the pattern.
Troubleshooting
| Problem | Fix |
|---|---|
| Search misses obvious matches | Mixed lookalikes. Check the code points and replace consistently. |
| A number won’t sum in a spreadsheet | Probably a minus sign U+2212 rather than a hyphen. Replace it. |
| Code looks correct and won’t compile | Check for a Greek question mark U+037E posing as a semicolon, or Cyrillic letters in identifiers. |
A browser shows xn-- in the address bar | Working as intended — the domain mixes scripts and is being shown in punycode. |
| Two list entries look identical | They contain different characters. Compare code points, not appearance. |
| Normalising didn’t merge them | Expected. Normalisation only merges canonically equivalent characters. |
| Spell-check flags a correctly spelled word | A letter from another script is hiding in it. |
Recap
| Situation | Do this |
|---|---|
| Unsure what a character is | Symbol Identifier — check the code point |
| Validating usernames or domains | Restrict to one script; compare confusable skeletons |
| Cleaning a document | Find and replace by code point, not by eye |
| Writing mathematics | The operator, not the Greek letter — ∑ not Σ |
Seeing xn-- in a URL | Read it carefully; that’s the warning |
| Relying on normalisation | Don’t — it merges almost none of these |
Frequently asked questions
What are lookalike characters?
Different Unicode characters that render almost identically — Cyrillic а and Latin a, the micro sign and Greek mu, a hyphen and a minus sign. They’re separate characters with separate code points, so they don’t match each other in search, sorting or comparison.
Why does Unicode include duplicates?
Three reasons: different writing systems genuinely share letterforms, some shapes have different meanings that software must distinguish, and characters from older encodings were preserved when those encodings were absorbed so that converted text could round-trip.
Does Unicode normalization fix lookalikes?
Almost never. Normalisation merges characters Unicode considers canonically equivalent. Lookalikes are different characters that share a shape, so they survive every normalisation form — only a couple of compatibility duplicates get merged.
What is a homograph attack?
Registering a domain that uses characters from another script to render identically to a real one. Browsers defend against it by displaying the punycode form of the address when the scripts look suspicious.
How can I tell which character I’ve got?
Check its code point with a Unicode identifier tool. Visual inspection is exactly what lookalikes are designed to defeat, so no amount of careful reading will settle it.
How do I stop lookalikes getting into my system?
Restrict identifier fields to a single script, flag any value that mixes scripts, and compare values using Unicode’s confusables data rather than raw string equality.
Written by Igor R. Tested on Windows 11, macOS and Linux, in Chrome, Firefox and Python. Last updated September 2026. Symbol code points follow the Unicode Standard.