<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cg S</title>
    <description>The latest articles on DEV Community by Cg S (@cgsdw).</description>
    <link>https://dev.to/cgsdw</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4182299%2Fcfb0c9dc-6261-4b58-9873-93fe93f25207.png</url>
      <title>DEV Community: Cg S</title>
      <link>https://dev.to/cgsdw</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cgsdw"/>
    <language>en</language>
    <item>
      <title>Invisible Characters Are Hiding in Your Copy-Pasted Text</title>
      <dc:creator>Cg S</dc:creator>
      <pubDate>Tue, 13 Oct 2026 13:31:09 +0000</pubDate>
      <link>https://dev.to/cgsdw/invisible-characters-are-hiding-in-your-copy-pasted-text-4931</link>
      <guid>https://dev.to/cgsdw/invisible-characters-are-hiding-in-your-copy-pasted-text-4931</guid>
      <description>&lt;p&gt;You copy a line of text. You run it. It fails. You look again — and it looks &lt;em&gt;identical&lt;/em&gt; to the version that worked.&lt;/p&gt;

&lt;p&gt;Same characters, different result. What's going on?&lt;/p&gt;

&lt;p&gt;Nine times out of ten, the culprit is an &lt;strong&gt;invisible character&lt;/strong&gt; — a real Unicode code point that occupies space in your string but renders as nothing on screen.&lt;/p&gt;

&lt;p&gt;Here's what they are, where they come from, and how to get rid of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are invisible characters?
&lt;/h2&gt;

&lt;p&gt;Invisible (or "zero-width") characters are real characters that take up no visible space. They're legitimately defined in Unicode, so your editor, language, and compiler treat them as ordinary input — you just can't see them.&lt;/p&gt;

&lt;p&gt;The usual suspects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;U+200B  ZERO WIDTH SPACE
U+200C  ZERO WIDTH NON-JOINER (ZWNJ)
U+200D  ZERO WIDTH JOINER (ZWJ)
U+2060  WORD JOINER
U+FEFF  ZERO WIDTH NO-BREAK SPACE (also used as a byte-order mark)
U+00A0  NO-BREAK SPACE (not zero-width, but easy to miss)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a family of &lt;strong&gt;bidirectional control characters&lt;/strong&gt; (&lt;code&gt;U+202A&lt;/code&gt;–&lt;code&gt;U+202E&lt;/code&gt;, &lt;code&gt;U+2066&lt;/code&gt;–&lt;code&gt;U+2069&lt;/code&gt;) that change how text is &lt;em&gt;displayed&lt;/em&gt; without changing the underlying bytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick way to spot one
&lt;/h2&gt;

&lt;p&gt;Put two strings side by side and compare their lengths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;          &lt;span class="c1"&gt;# 5 characters
&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;he&lt;/span&gt;&lt;span class="se"&gt;\u200b&lt;/span&gt;&lt;span class="s"&gt;llo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;    &lt;span class="c1"&gt;# looks like "hello" too, but...
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 5
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a comparison fails and you'd swear the strings are equal, count the characters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do they come from?
&lt;/h2&gt;

&lt;p&gt;You almost never type them on purpose. They sneak in when you copy and paste:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web pages&lt;/strong&gt; — some sites inject zero-width characters for tracking, layout, or "copy protection."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI chat interfaces&lt;/strong&gt; — text copied from LLMs often carries non-breaking spaces and other unusual Unicode, and zero-width characters show up too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDFs and Word documents&lt;/strong&gt; — soft hyphens, non-breaking spaces, and direction marks survive copy-paste.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CMSs and form fields&lt;/strong&gt; — rich-text editors love to add "helpful" invisible formatting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deliberately&lt;/strong&gt; — for watermarking, fingerprinting, or even attacks like &lt;strong&gt;Trojan Source&lt;/strong&gt; (CVE-2021-42574), where invisible direction characters make source code &lt;em&gt;look&lt;/em&gt; one way and &lt;em&gt;compile&lt;/em&gt; another.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why they cause real problems
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code that won't compile&lt;/strong&gt; — an identifier with a hidden zero-width character is a &lt;em&gt;different&lt;/em&gt; identifier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failing string comparisons&lt;/strong&gt; — &lt;code&gt;"abc" == "abc\u200b"&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken search and replace&lt;/strong&gt; — you can't find what you can't see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git diffs that drive you crazy&lt;/strong&gt; — a line that "didn't change" still shows up as modified.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weird SEO and form validation&lt;/strong&gt; — invisible text is a classic spam signal, and a required field can be "filled" by characters no human typed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to find and remove them
&lt;/h2&gt;

&lt;p&gt;Pick whatever fits your workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A detector tool&lt;/strong&gt; — paste the text and see every invisible character highlighted, with its code point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your editor&lt;/strong&gt; — check for "show whitespace / render special characters." Zero-width characters are the tricky ones; they usually need a plugin or a hex view.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The command line&lt;/strong&gt; — a quick check on Linux/macOS:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;{200B}-&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;{200D}&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;{FEFF}]"&lt;/span&gt; file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A script&lt;/strong&gt; — once you know the code points, stripping them is one line:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="n"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[\u200b\u200c\u200d\u2060\ufeff]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;If something &lt;em&gt;looks&lt;/em&gt; right but &lt;em&gt;behaves&lt;/em&gt; wrong, stop trusting your eyes and start counting characters. Invisible characters are the most common cause — and they're easy to remove once you can finally see them.&lt;/p&gt;

&lt;p&gt;I built a small, free, browser-based tool for exactly this: it scans text for invisible and zero-width characters, shows you where they are, and lets you clean the text in one click. Everything runs locally in your browser — nothing is uploaded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it here: &lt;a href="https://ai-watermark-detector.com" rel="noopener noreferrer"&gt;https://ai-watermark-detector.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If this was useful, tell me in the comments which invisible character caused you the most trouble.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>unicode</category>
    </item>
  </channel>
</rss>
