<?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: Noam Brand</title>
    <description>The latest articles on DEV Community by Noam Brand (@noam_brand_e9641722eccabc).</description>
    <link>https://dev.to/noam_brand_e9641722eccabc</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%2F1848930%2Fe6dfc286-1c2a-444b-a689-a9f2289c4e14.jpg</url>
      <title>DEV Community: Noam Brand</title>
      <link>https://dev.to/noam_brand_e9641722eccabc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/noam_brand_e9641722eccabc"/>
    <language>en</language>
    <item>
      <title>VLOOKUP says #N/A, COUNTIF says the value is there: 18 look-alike pairs tested in a real Excel</title>
      <dc:creator>Noam Brand</dc:creator>
      <pubDate>Sun, 18 Oct 2026 06:19:09 +0000</pubDate>
      <link>https://dev.to/noam_brand_e9641722eccabc/vlookup-says-na-countif-says-the-value-is-there-18-look-alike-pairs-tested-in-a-real-excel-5ck6</link>
      <guid>https://dev.to/noam_brand_e9641722eccabc/vlookup-says-na-countif-says-the-value-is-there-18-look-alike-pairs-tested-in-a-real-excel-5ck6</guid>
      <description>&lt;p&gt;You can see the customer number in both sheets. VLOOKUP returns #N/A. You wrap the value in TRIM, as every forum thread tells you to, and it still returns #N/A.&lt;/p&gt;

&lt;p&gt;I wanted to know which invisible differences really break a lookup, so I stopped guessing and asked Excel. I put 18 pairs of look-alike values into a workbook, one pair per row, and had a hidden copy of Excel (Microsoft 365, build 16.0.20430) calculate VLOOKUP, XLOOKUP, MATCH, the &lt;code&gt;=&lt;/code&gt; operator and COUNTIF on each pair. The last two columns run VLOOKUP again after cleaning the left value with TRIM, then with TRIM and CLEAN.&lt;/p&gt;

&lt;p&gt;1 means Excel found the match. 0 means #N/A or FALSE.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Left value vs right value&lt;/th&gt;
&lt;th&gt;VLOOKUP&lt;/th&gt;
&lt;th&gt;XLOOKUP&lt;/th&gt;
&lt;th&gt;MATCH&lt;/th&gt;
&lt;th&gt;&lt;code&gt;=&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;COUNTIF&lt;/th&gt;
&lt;th&gt;TRIM&lt;/th&gt;
&lt;th&gt;TRIM + CLEAN&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trailing space: &lt;code&gt;Smith&lt;/code&gt; vs &lt;code&gt;Smith&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Double space: &lt;code&gt;John  Smith&lt;/code&gt; vs &lt;code&gt;John Smith&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tab between the words&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Line break inside the cell&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-breaking space (code 160)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Narrow no-break space (U+202F)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-width space at the end (U+200B)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Left-to-right mark at the start (U+200E)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right-to-left mark before a number stored as text&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Byte-order mark at the start (U+FEFF)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Soft hyphen inside the word (U+00AD)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text &lt;code&gt;"12345"&lt;/code&gt; vs the number 12345&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text &lt;code&gt;"012345678"&lt;/code&gt; vs the number 12345678&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Curly apostrophe (U+2019) vs straight, in O'Brien&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dash (U+2013) vs hyphen, in A-100&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ACME&lt;/code&gt; vs &lt;code&gt;acme&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hebrew name with a left-to-right mark&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hebrew name with a non-breaking space&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Fifteen of those rows are real differences. VLOOKUP found none of them. Three results surprised me.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. TRIM fixed 2 of the 15
&lt;/h2&gt;

&lt;p&gt;Trailing spaces and double spaces. That's it. The non-breaking space, which tends to arrive with text pasted from a web page or a system export, survives TRIM untouched.&lt;/p&gt;

&lt;p&gt;CLEAN didn't rescue the tab or the line break either. It deletes those characters instead of turning them into a space, so &lt;code&gt;John&lt;/code&gt; + line break + &lt;code&gt;Smith&lt;/code&gt; becomes &lt;code&gt;JohnSmith&lt;/code&gt;, and that still misses &lt;code&gt;John Smith&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. XLOOKUP is not just VLOOKUP with nicer syntax
&lt;/h2&gt;

&lt;p&gt;XLOOKUP ignored four invisible marks that VLOOKUP and MATCH treat as real characters: the left-to-right mark, the right-to-left mark, the byte-order mark and the soft hyphen. The &lt;code&gt;=&lt;/code&gt; operator and COUNTIF ignored them too.&lt;/p&gt;

&lt;p&gt;So two sheets that look up the same data, one with VLOOKUP and one with XLOOKUP, can report a different number of matches. Neither one is broken. They compare text differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. COUNTIF says yes when every lookup says no
&lt;/h2&gt;

&lt;p&gt;COUNTIF read the text &lt;code&gt;"012345678"&lt;/code&gt; as the number 12345678 and counted it. Every lookup function returned #N/A for the same pair.&lt;/p&gt;

&lt;p&gt;That explains a classic support question: "COUNTIF says the value is in the list, so why does VLOOKUP fail?" COUNTIF converts text that looks like a number. The lookups don't.&lt;/p&gt;

&lt;p&gt;One more I didn't expect. In the two Hebrew rows, every function ignored the direction mark and the non-breaking space. In English text, the same characters broke VLOOKUP. Excel's comparison depends on the script the text is written in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the hidden character yourself
&lt;/h2&gt;

&lt;p&gt;In Microsoft 365 this formula lists the code of every character in A2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=TEXTJOIN(" ",TRUE,UNICODE(MID(A2,SEQUENCE(LEN(A2)),1)))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Smith&lt;/code&gt; with a zero-width space at the end comes back as &lt;code&gt;83 109 105 116 104 8203&lt;/code&gt;. The usual suspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;160: non-breaking space&lt;/li&gt;
&lt;li&gt;8203: zero-width space&lt;/li&gt;
&lt;li&gt;8206 and 8207: direction marks&lt;/li&gt;
&lt;li&gt;8217: curly apostrophe&lt;/li&gt;
&lt;li&gt;65279: byte-order mark&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I measured instead of trusting the forums
&lt;/h2&gt;

&lt;p&gt;This started with a match checker I built for people who live in Excel. It looks up each value the way VLOOKUP does and, when Excel would miss it, says why: extra spaces, a number stored as text, a look-alike dash or quote, a hidden character, a typo. It changes nothing until you approve a fix, and the fix goes into a new copy of the file. The original is never written.&lt;/p&gt;

&lt;p&gt;The part I'd reuse in any spreadsheet tool: when Excel is on the machine, the checker asks a hidden, separate Excel to confirm each finding, so Excel has the last word instead of my rules. The table above came from the same trick. Write the pairs to a workbook, let a hidden Excel calculate, read the results back.&lt;/p&gt;

&lt;p&gt;Which of these has cost you the most time?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: Claude, an AI assistant, wrote this article and ran the measurement in Excel on my machine. The 1s and 0s in the table are Excel's own output.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>excel</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>data</category>
    </item>
    <item>
      <title>I stopped pasting VBA into a chatbot and pointed the agent at the actual workbook</title>
      <dc:creator>Noam Brand</dc:creator>
      <pubDate>Mon, 14 Sep 2026 15:03:51 +0000</pubDate>
      <link>https://dev.to/noam_brand_e9641722eccabc/i-stopped-pasting-vba-into-a-chatbot-and-pointed-the-agent-at-the-actual-workbook-aen</link>
      <guid>https://dev.to/noam_brand_e9641722eccabc/i-stopped-pasting-vba-into-a-chatbot-and-pointed-the-agent-at-the-actual-workbook-aen</guid>
      <description>&lt;p&gt;Here is the loop most people are stuck in when they use AI to fix a macro. Open the VBA editor, copy the sub out, paste it into a chat window, describe the problem, paste the answer back, run it, watch it throw error 1004 on some object it never saw, copy the error back, and go again. It works, sort of, but you are the integration layer. You are the clipboard, the compiler, and the test harness, doing it all by hand.&lt;/p&gt;

&lt;p&gt;That loop is worse for VBA than for almost any other language, and the reason is specific.&lt;/p&gt;

&lt;h2&gt;
  
  
  The chatbot is guessing in the dark
&lt;/h2&gt;

&lt;p&gt;Most code lives in files a model can read. VBA lives inside the workbook. Its behavior depends on the object model, the references that are enabled, the names on the sheets, and the actual state of the Excel instance that is open right now. When you paste one sub into a chat window, you have thrown all of that away. The model writes something that looks right, but it can't see the other module that already declared that variable, and it can't run the macro to find out that the range you handed it is one row short.&lt;/p&gt;

&lt;p&gt;So it guesses. Sometimes the guess is good. Often it compiles and then does the wrong thing quietly, which is the worst outcome, because now you are debugging code you didn't write against data the model never saw.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes when the agent can actually run it
&lt;/h2&gt;

&lt;p&gt;The fix is not a smarter chatbot. It is closing the loop, letting the thing that writes the code also read your real modules and run them.&lt;/p&gt;

&lt;p&gt;Point an agent at the project itself: the .bas and .cls files, and the workbook that is open. You describe the change. It reads your actual modules, writes the code, runs the macro against the live workbook, and reads whatever comes back. If it throws, it sees the real runtime error, on your machine, against your data, and takes another pass before it ever hands the code to you. You review a result and a diff instead of playing clipboard.&lt;/p&gt;

&lt;p&gt;The part that still gets me is watching it run the macro and react. Not "here is a macro that should work." It runs, it sees row 1,001 is blank, it fixes the bound, it runs again. That is the difference between an assistant that talks about your code and one that works on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters if you own a pile of VBA
&lt;/h2&gt;

&lt;p&gt;Most serious VBA is old. Someone built a 4,000-line workbook years ago, they left, and now it is load-bearing and nobody wants to touch it. That is exactly the code where the paste-into-a-chatbot approach falls apart, because the context that matters is spread across modules and sheets you can't paste. An agent that reads the whole project and tests against the real file is the only version of "AI help" that survives contact with that kind of workbook.&lt;/p&gt;

&lt;p&gt;Full disclosure: I built a tool for this, called VBridge. It runs Claude Code against your actual Excel VBA project on your own machine. It is not the only way to close the loop, but closing the loop is the point, so even if you wire up your own, do that part.&lt;/p&gt;

&lt;p&gt;A two-minute before and after is here: vbridge.netlify.app&lt;/p&gt;

&lt;p&gt;If you write VBA, or you inherited a workbook nobody wants to own, try describing a change and letting the agent run it against the real file. Going back to the chat window afterward feels like going back to hand-copying error messages, because that is what it is.&lt;/p&gt;

</description>
      <category>vba</category>
      <category>excel</category>
      <category>ai</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
