Invisible zero-width characters that break code (U+200B, BOM, bidi)

The characters are there. Your eyes just cannot see them — until a password fails, a test flakes, or a phishing lookalike slips past a review.

The usual suspects

CharacterCode pointUTF-8 bytesWhy it hurts
Zero-width space (ZWSP)U+200BE2 80 8BBreaks identifiers, URLs, "equal" strings that look identical
Zero-width non-joiner / joinerU+200C / U+200DE2 80 8C / 8DZWNJ surprises in names; ZWJ builds emoji sequences
BOM / zero-width no-break spaceU+FEFFEF BB BF (UTF-8 BOM)File starts with invisible bytes; JSON parsers and shebangs choke
Soft hyphenU+00ADC2 ADCopied from Word/PDF; appears only at line wraps
Non-breaking spaceU+00A0C2 A0Looks like a normal space; fails == " " and some split/trim
Bidi overrides (RLO/LRO/PDF…)U+202A–U+202E, U+2066–U+2069variesCan visually reverse text — Trojan Source / phishing lookalikes
Tag charactersU+E0001, U+E0020–U+E007F4-byte UTF-8Mostly obsolete tags; can hide in "empty" looking strings

Interactive emoji / ZWJ walkthrough: Wirebook Who stole the emojis?. Invisible-character lens: Wirebook ghost footprints. Machine-readable report for any paste: Anvil /v1/text/inspect (flags invisible, bom, bidi_override, homoglyph).

How they sneak in

How to spot them

# Python
import re
cleaned = re.sub(r"[\u200B-\u200D\uFEFF\u00AD\u2060-\u2064\u2066-\u2069\u202A-\u202E]", "", text)
cleaned = cleaned.replace("\u00a0", " ")  # NBSP → normal space

# JavaScript
const cleaned = text
  .replace(/[\u200B-\u200D\uFEFF\u00AD\u2060-\u2064\u2066-\u2069\u202A-\u202E]/g, "")
  .replace(/\u00a0/g, " ");

Do not blindly strip U+200D (ZWJ) from user-facing emoji text — that is what glues family/profession emoji together (see the tofu guide). Strip ZWJ only in identifiers, tokens and security-sensitive comparisons.

Fix it at the source

Related

Why one hidden character halves a text message · SMS length counter · Removing spaces and odd characters from file names · Invisible characters in a password · Strings that look the same but are not equal · Non-breaking space vs space · Look-alike (homoglyph) characters · Mojibake / ðŸ / é · Emoji shows as □ / tofu · MySQL Incorrect string value / utf8mb4 · Wirebook emoji lesson · Anvil text X-ray API

Questions

What is a zero-width space?

U+200B is a character that takes no space on screen but still counts in string length and breaks identifiers, URLs and equality checks. It often arrives via copy-paste.

Why does my JSON file fail to parse at the first character?

A UTF-8 BOM (U+FEFF, bytes EF BB BF) at the start is a common cause. Re-save as UTF-8 without BOM.

How do I remove invisible Unicode in Python or JavaScript?

Use a regex over U+200B–U+200D, U+FEFF, soft hyphen, and bidi marks; map U+00A0 to a normal space. Keep ZWJ if you need intact emoji sequences.

Are bidi overrides dangerous?

They can visually reorder source code or URLs (Trojan Source / phishing). Flag U+202A–U+202E and U+2066–U+2069 in reviews and CI.

Is a non-breaking space the same as a space?

No. U+00A0 looks identical but is a different code point, so trim/split/equality can fail. Normalise it to U+0020 when comparing.

How can I inspect a string for invisibles?

Use Anvil text X-ray (https://api.oddlathe.com/docs/text-inspect), an editor Unicode highlight, xxd/hex dump, or the word-counter and text-diff tools.

Published · updated .

Try it

Related guides

Newest guides