📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

Customers report names like "José" in a CSV export opened in Excel, and an imported file shows "Jos�". How do you diagnose and fix these encoding problems?

A practical debugging walkthrough for mojibake and replacement characters: reading the bytes in hex, identifying UTF-8 versus Windows-1252 and double encoding, the BOM and Excel, and fixing both export and import paths in .NET.

Mid-Level Debugging Intermediate computer-basics csv debugging encoding mojibake utf-8

🎯 Key points

  • Look at the raw bytes in hex at each hop before changing any code
  • é means UTF-8 bytes (C3 A9) were decoded as Windows-1252 or Latin-1; � (U+FFFD) means bytes were not valid in the decoding encoding, often Windows-1252 read as UTF-8
  • Excel's CSV behaviour depends on how the file is opened and on the Windows locale: double-clicking a BOM-less CSV uses the system ANSI code page, while Data → From Text/CSV lets the user choose UTF-8
  • Because you cannot control users' locale or Excel settings, write UTF-8 with a BOM for Excel-bound CSV, or export .xlsx
  • Read legacy files with the right code page (register CodePagesEncodingProvider) or detect and reject invalid UTF-8
  • Repair data already damaged (double encoding) separately and carefully, on proven rows only, with a backup
  • Make encodings explicit at every boundary and add tests with non-ASCII data

⏱ 30-second answer

First I look at the bytes, not the screen. José is classic mojibake: the file contains UTF-8 (é is C3 A9) but the reader decoded it as Windows-1252. With Excel on Windows this usually happens when a user double-clicks a CSV that has no BOM: Excel then uses the system's legacy (ANSI) code page, which depends on the Windows locale (1252 for English, 1258 for Vietnamese, and UTF-8 only if the "Use Unicode UTF-8 for worldwide language support" option is turned on). Importing through Data → From Text/CSV lets the user pick UTF-8, but most users double-click. The reliable fix for the export is to write UTF-8 with a BOM (new UTF8Encoding(true)) or produce .xlsx. Jos� is the opposite: a Windows-1252 file (é is the single byte E9) read as UTF-8, which is invalid, so the decoder substitutes U+FFFD. The fix is to read with the right encoding or reject invalid UTF-8 with a clear error. Then I check whether bad data was already saved and repair it separately.

🗣 Full answer (senior)

Step 1: get the bytes

Screens lie; bytes don't. Take a failing file (or the HTTP response, or the database value) and hex-dump the bad region.

You see Bytes for "é" Meaning
é correct C3 A9 read as UTF-8 Fine
é C3 A9 read as Windows-1252 UTF-8 data, wrong decoder (mojibake)
� or ? E9 read as UTF-8 Windows-1252 data, wrong decoder (invalid UTF-8)
é C3 83 C2 A9 Double encoding: mojibake was saved and re-encoded as UTF-8
? in the database 3F Lost for good: written through Encoding.ASCII or a non-Unicode column

Step 2: find which boundary is wrong

Walk the path: source system → file or API → our reader → our model → database → our writer → consumer (Excel, browser, partner). Check the encoding at each hop. The bug is at the hop where the bytes stop matching what the next reader expects.

Case A: exporting CSV for Excel

  • .NET writes UTF-8 without a BOM by default (File.WriteAllText, StreamWriter).
  • Excel on Windows opens a double-clicked CSV without a BOM using the system ANSI code page (Windows-1252 for English locales), so C3 A9 shows as é.
  • Fix: write the BOM explicitly with new UTF8Encoding(encoderShouldEmitUTF8Identifier: true), or, from an API, prepend Encoding.UTF8.Preamble to the stream. The HTTP charset=utf-8 header doesn't help, because Excel only sees the saved file.
  • Alternatives: generate .xlsx (no encoding ambiguity), or tell users to use Data → From Text/CSV and pick UTF-8.

Case B: importing a partner's file

  • The partner's system exports Windows-1252. Reading it as UTF-8 turns every non-ASCII byte into �, silently.
  • Fix: agree on UTF-8 in the interface contract. Until then, read with the right code page: Encoding.RegisterProvider(CodePagesEncodingProvider.Instance) then Encoding.GetEncoding(1252).
  • Detect problems instead of hiding them: validate with Utf8.IsValid or decode with a throwing decoder (new UTF8Encoding(false, throwOnInvalidBytes: true)), and reject the file with a clear message.

Case C: repairing data already damaged

  • Mojibake that was saved can sometimes be reversed: encode the bad string as Windows-1252 to get the original bytes, then decode those bytes as UTF-8. Only do this on rows you've proved are damaged, after a backup, because running it on correct data damages it.
  • ? or � substitutions are lossy and can't be reversed; recover from the original source files.

Production example

A payroll export for a Sydney client showed Nguyá»…n instead of Nguyễn for hundreds of staff. The CSV was correct UTF-8; Excel was the reader. Adding a BOM fixed new exports. Separately, an older import path had saved some names double-encoded, which a targeted repair script fixed after comparing against the HR system.

🔍 What the interviewer is checking

Checks practical debugging skill on a common, user-visible problem.

Green flags: starts with the bytes, recognises mojibake versus replacement characters on sight, knows the Excel BOM behaviour, handles legacy code pages correctly, separates the fix from data repair, adds tests.

Red flags: "just use UTF-8 everywhere" without diagnosing; repairs data blindly; doesn't know defaults differ between tools.

📚 Learn in depth

1. The decoding matrix

                 decoder
               UTF-8          Windows-1252
  bytes   +----------------+-----------------+
  UTF-8   |  correct  é    |  mojibake  é   |
  (C3 A9) |                |                 |
          +----------------+-----------------+
  1252    |  invalid  �    |  correct  é     |
  (E9)    |                |                 |
          +----------------+-----------------+

Two symptoms, two opposite causes. Recognising the pattern on sight saves hours.

2. Double encoding, step by step

"é" --UTF-8--> C3 A9 --read as 1252--> "é" --saved as UTF-8--> C3 83 C2 A9

Each round trip makes the text longer and stranger. Seeing àpatterns means the data went through the wrong decoder at least twice.

3. Tools for looking at bytes

Where Tool
File, Linux or macOS xxd file.csv \| head, file -i file.csv
File, Windows Format-Hex file.csv in PowerShell
File, editor VS Code shows the encoding in the status bar and can "Reopen with Encoding"
.NET Convert.ToHexString(Encoding.UTF8.GetBytes(value)) in a debugger watch
SQL Server SELECT CONVERT(varbinary(100), Name) shows UTF-16LE for NVARCHAR
HTTP Browser dev tools or Fiddler: check Content-Type charset and the raw body

4. Where .NET picks encodings

API Default
File.ReadAllText(path) Detects a BOM, otherwise UTF-8
File.WriteAllText(path, text) UTF-8 without a BOM
new StreamWriter(path) UTF-8 without a BOM
Encoding.UTF8 used to write Emits a BOM with StreamWriter and File.WriteAllText
Console.Out on Windows The console code page unless you set Console.OutputEncoding
ASP.NET Core text responses UTF-8

5. Databases

  • SQL Server VARCHAR with a non-UTF-8 collation stores single-byte code page data; Unicode characters outside that code page become ? on insert. Use NVARCHAR (or a _UTF8 collation on SQL Server 2019+).
  • Parameters: EF Core and SqlClient send string as NVARCHAR by default; mapping a property to varchar (IsUnicode(false)) is a deliberate choice that loses characters outside the code page.

6. A repeatable investigation checklist

  1. Reproduce with one record and capture the raw bytes at each hop.
  2. Identify the pattern (mojibake, replacement, double encoding, lossy ?).
  3. Find the first hop where bytes and expected encoding disagree.
  4. Fix that hop by making the encoding explicit.
  5. Assess damaged data: reversible or not, how many rows, which time range.
  6. Add a regression test with names like José, Nguyễn, Māori, 王芳 and an emoji.

7. Try it yourself

  1. Write José to a CSV with File.WriteAllText and open it in Excel by double-clicking. Then write it with new UTF8Encoding(true) and open it again.
  2. Save a file as Windows-1252 from VS Code, read it with File.ReadAllText, and print the hex of the result. Then read it correctly with Encoding.GetEncoding(1252).
  3. Simulate double encoding in code and write a function that detects and reverses it, with tests proving it leaves correct text alone.

💻 Code

namespace EncodingFixes;

using System.Text;
using System.Text.Unicode;

public static class CsvEncoding
{
    // Excel-friendly export: UTF-8 with a byte order mark.
    public static async Task WriteForExcelAsync(string path, IEnumerable<string[]> rows, CancellationToken ct)
    {
        await using var writer = new StreamWriter(path, append: false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
        foreach (var row in rows)
        {
            await writer.WriteLineAsync(string.Join(',', row.Select(Escape)).AsMemory(), ct);
        }
    }

    // Strict import: reject anything that isn't valid UTF-8 instead of silently inserting U+FFFD.
    public static string ReadUtf8Strict(byte[] content)
    {
        if (!Utf8.IsValid(content))
        {
            throw new InvalidDataException("File is not valid UTF-8. Ask the sender to export as UTF-8.");
        }
        var strict = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
        ReadOnlySpan<byte> span = content;
        if (span.StartsWith(Encoding.UTF8.Preamble))
        {
            span = span[Encoding.UTF8.Preamble.Length..];
        }
        return strict.GetString(span);
    }

    // Legacy partner that still sends Windows-1252.
    public static string ReadWindows1252(byte[] content)
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        return Encoding.GetEncoding(1252).GetString(content);
    }

    // Reverses one layer of UTF-8-read-as-1252 mojibake. Only run on rows proven to be damaged.
    public static string RepairMojibake(string damaged)
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        byte[] originalBytes = Encoding.GetEncoding(1252).GetBytes(damaged);
        return Encoding.UTF8.GetString(originalBytes);
    }

    private static string Escape(string field) =>
        field.IndexOfAny([',', '"', '\n', '\r']) >= 0 ? $"\"{field.Replace("\"", "\"\"")}\"" : field;
}
# Inspect the first bytes of a file
xxd export.csv | head -n 3
# 00000000: efbb bf4e 616d 652c ...   <- EF BB BF is the UTF-8 BOM

⚠ Common mistakes

  • Mistake: changing encodings in code until the screen looks right. Trial and error often fixes one viewer while breaking another, and can double-encode stored data. Instead: hex-dump the bytes, identify the pattern, and fix the specific boundary that's wrong.

  • Mistake: having no encoding standard across systems. When each service, file feed and team picks its own default, every new integration is a fresh chance for mojibake, and nobody knows which side is wrong. Instead: make UTF-8 the documented standard for files, APIs and queues, list any legacy exceptions in the interface specification, and validate at ingestion.

  • Mistake: setting charset=utf-8 on the HTTP response and expecting Excel to honour it. The header is lost once the file is saved; Excel only sees the bytes. Instead: add a UTF-8 BOM for CSVs meant for Excel, or produce .xlsx.

  • Mistake: decoding untrusted files with the default UTF-8 decoder and carrying on. Invalid bytes silently become �, and damaged names are stored permanently. Instead: validate with Utf8.IsValid or a throwing decoder, and reject or route the file for handling.

  • Mistake: running a mojibake repair over the whole table. On correct rows it turns valid text into garbage, which is worse than the original bug. Instead: identify damaged rows precisely (pattern match and source comparison), back up, repair those rows only, and verify.

  • Mistake: leaving the database layer's encoding implicit. A VARCHAR column with a legacy collation, or an EF Core property mapped with IsUnicode(false), turns characters outside the code page into ? on insert, and the information is gone. Instead: use NVARCHAR (or a _UTF8 collation) for human text, and review column types and IsUnicode mappings as part of the schema design.

  • Mistake: testing only with English data. Encoding bugs stay hidden until a real customer with an accented or non-Latin name appears. Instead: keep a standard set of test names with accents, macrons, CJK and emoji in every import and export test.

🏛 Senior insight: trade-offs

This is a diagnosis question disguised as a trivia question. Seniors follow evidence (bytes at each hop) instead of guessing, distinguish reversible damage (mojibake) from irreversible loss (?, �), and treat repairing stored data as a separate, risk-managed change. They also fix the process: interface contracts that state UTF-8, strict decoding at ingestion, and test data that reflects real customers. In Australia that matters for privacy too: a corrupted name is inaccurate personal information under APP 10.

🔗 Follow-ups the interviewer may ask

  1. When: Candidate identifies C3 A9 as the UTF-8 bytes for é. Why: Checks the underlying understanding of the two encodings involved.
    How to answer

    Both encode Unicode code points. UTF-8 uses 1–4 bytes per code point and is ASCII-compatible, so it is compact for English (half the memory of UTF-16) and dominates files, HTTP and JSON. UTF-16 uses 2 bytes for the Basic Multilingual Plane and 4 bytes (a surrogate pair) above U+FFFF, such as most emoji; it is more compact than UTF-8 for CJK text (2 bytes instead of 3). .NET strings are UTF-16, and string.Length counts UTF-16 code units, not characters: "👍".Length is 2, "👍🏽".Length is 4, and "é" is 1 if precomposed (U+00E9) but 2 if written as e + combining accent. Count what you mean: EnumerateRunes() for code points, StringInfo for user-visible characters, Encoding.UTF8.GetByteCount for byte limits. On high-traffic paths, stay in UTF-8 bytes (u8 literals, spans, Utf8JsonReader) so you do not transcode and allocate on every request.

  2. When: Candidate talks about making encodings explicit at boundaries. Why: Tests the same skills in a binary protocol where the field size is in bytes.
    How to answer

    Several real bugs. Encoding.ASCII turns José into Jos?, so names are corrupted. The field limit is 32 bytes, but the code truncates at 32 chars; once you fix the encoding to UTF-8, a 32-char name can be up to 96 bytes and overflows the buffer. Substring can also split a surrogate pair. BitConverter writes the machine's little-endian order, but the partner specification says big-endian, so every ID and length is wrong. I'd rewrite it with BinaryPrimitives, UTF-8 truncated on grapheme boundaries to 32 bytes, input validation, and golden-byte tests from the partner's specification.

🛠 Real-world problems: how you handle them

Payroll CSV export garbles Vietnamese names in Excel
Situation
A payroll SaaS product exported staff lists as CSV for about 300 Australian clients. A large client with many Vietnamese staff reported names such as "Nguyá»…n" in Excel just before a pay run.
Signals
Support tickets with screenshots from Excel; the same file looked correct in VS Code and in Google Sheets; database values looked correct in the admin UI.
Investigation
Downloaded the export and ran `xxd` on it: valid UTF-8 with no BOM. Reproduced by double-clicking the file in Excel on Windows with an English locale. Also queried the database with `CONVERT(varbinary, Name)` and found 41 older rows that were double-encoded by a previous import path.
Resolution
Changed the export to write a UTF-8 BOM, and added an optional `.xlsx` export. Wrote a targeted repair script for the 41 double-encoded rows, verified against the HR source system, after a backup.
Prevention
Export and import tests with a standard multilingual name set; strict UTF-8 validation on all imports; documented encodings in the partner file specifications.

How to tell it (STAR)

**Situation:** At [company], a major payroll client saw garbled Vietnamese names in our CSV export [two days] before a pay run. **Task:** I had to fix it quickly without damaging stored data. **Action:** I hex-dumped the file and saw valid UTF-8 without a BOM, so the data was right and Excel was decoding it as Windows-1252. I added a BOM to the export. While checking the database bytes I also found [41] rows double-encoded by an older import, which I repaired with a targeted, backed-up script verified against the HR system. **Result:** The client ran payroll on time, and I added multilingual test data and strict UTF-8 validation so the same class of bug was caught in CI.

↩ Usually asked after

More in Computer Basics

Last updated 23 Oct 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me