CSV vs Excel: which one to hand someone, and which one to keep

They hold the same rows, so the choice looks like taste. It is not — one of them rewrites your data on the way in, and the other cannot describe what your data means.

Both hold rows and columns, both open on any machine in the building, and both round-trip through every tool anyone on the team owns. So the choice gets made on habit — whichever the last export used — and the difference only surfaces later, when a reference code that started as 007 comes back as 7.

The useful way to compare them is not by feature list. It is by asking what each one loses, because they lose completely different things and only one of the two losses is recoverable.

They are not the same kind of thing

Almost every practical difference falls out of one fact: CSV is a text file, and an .xlsx is a zip archive full of XML. Rename one and look inside:

An .xlsx is a zip, and always has been
unzip -l subscribers.xlsx

  xl/worksheets/sheet1.xml     the cells
  xl/styles.xml                number formats, fonts, colours
  xl/workbook.xml              sheet names and order
  docProps/core.xml            author, created, modified
  [Content_Types].xml

That structure is what lets a workbook carry things CSV has no slot for — several sheets, real cell types, formulas, formatting, frozen panes. It is also why a workbook cannot be appended to with >>, cannot be read a line at a time, and produces a useless diff in version control: change one cell and the compressed bytes of the whole archive change.

Failure 1: the spreadsheet retypes your data on open

This is the one that costs real money, and it happens at the moment of double-clicking — before you have edited anything. A CSV holding four perfectly ordinary values, read with type guessing on:

The CSV going in
ref,iccid,gene,when
007,8901240544102066246,SEPT1,3/4/2026
What a type-guessing reader hands back
{
  ref:   7,                      leading zero gone
  iccid: 8901240544102066000,    rounded at 15 digits
  gene:  "SEPT1",
  when:  46085                   now a date serial
}

Three of the four changed. The ref lost its padding because 007 looks like a number and numbers do not keep leading zeros. The iccid is the serious one: a 19-digit SIM identifier does not fit in the 15 significant digits a spreadsheet keeps, so the tail is replaced with zeros. Two SIMs one digit apart collapse onto the same value, and a deduplication step downstream will merge them without complaint.

The date is the sneakiest, because 46085 is not wrong so much as no longer text. Ask what day it landed on:

What 3/4/2026 actually became
serial 46085  ->  2026-03-04

Whoever wrote 3/4/2026 meaning the third of April, as most of the world would, now has the fourth of March. Nothing errored. The cell is a valid date, it is simply a different day, and there is no way to recover the intent from the file afterwards.

Worth being precise about where the fault sits: this is the application guessing, not the format. The CSV on disk still says 007. Any reader that treats every field as text — including CSV to Table on this site — gives all four values back unchanged. The damage happens when the guess is saved.

Failure 2: nobody agreed what CSV means

CSV's own failure is the opposite: it does not rewrite anything, it just declines to say what the file contains. RFC 4180 is Informational — it describes common practice rather than mandating it — so a file can be perfectly valid CSV and still unreadable by the thing you point at it.

The most common case is the separator. A German or French export uses semicolons, because in those locales the comma is already the decimal point. Read one with a comma parser:

Same bytes, two readings
name;city;note
Sigrid;Bergen;lives in NO

parsed as comma-separated:
  [["name;city;note"], ["Sigrid;Bergen;lives in NO"]]      1 column

parsed as semicolon-separated:
  [["name","city","note"], ["Sigrid","Bergen","lives in NO"]]   3 columns

No error either way. The comma reading is a valid one-column file that happens to be useless, which is why a broken CSV import so often shows up as "everything is in column A" rather than as a failure.

The thing nobody wrote downWhat variesWhat it breaks
Delimitercomma, semicolon, tab, pipeEvery column, silently
Quote handling"" doubled vs \" escapedAny field containing a quote
Line endingsCRLF vs LFA trailing \r on the last column of every row
Byte order markpresent or absentThe first header name, which stops matching
Header rowpresent or absentEither a lost record or a row of made-up column names
EncodingUTF-8, Latin-1, Windows-1252Every accented character

The BOM row is the one that wastes the most time, because the file looks correct in every editor. A UTF-8 byte order mark is three bytes — EF BB BF — sitting before the first character and rendering as nothing at all. So the first header is really "\uFEFFsubscriberId" while your code looks up "subscriberId", the two do not match, and both print identically in a terminal. If a script insists column one does not exist and you can plainly see that it does, this is almost always why.

An .xlsx has none of these arguments. Cells carry their type, the encoding is settled by the format, and there is no delimiter to guess. That is a genuine advantage and it is why exports aimed at non-technical recipients are usually better as workbooks.

Size, and the thing everyone assumes backwards

The intuition is that .xlsx must be smaller because it is compressed. Measured on the same 5,000-row subscriber export:

FormatBytesNotes
subscribers.csv225,037Plain text
subscribers.xlsx350,418Zipped XML — every cell wrapped in tags
subscribers.csv.gz26,822The same CSV, gzipped

The workbook is larger than the CSV, because the XML overhead per cell outweighs what the zip wins back. And the CSV compresses to a twelfth of its size, while the .xlsx is already a zip and will not compress meaningfully again. For anything crossing a network — an API response, a nightly export, an archive — gzipped CSV wins by an order of magnitude and it is not close.

What each one simply cannot do

CSVExcel (.xlsx)
Multiple sheetsNo — one table per fileYes
Cell typesNo — everything is textYes, stored per cell
FormulasNoYes
Formatting, colours, widthsNoYes
Read a line at a timeYesNo — the archive must be opened whole
Append without rewritingYesNo
Useful diff in gitYesNo — binary blob
Row limitNone1,048,576 per sheet
Edit with standard text toolsYes — grep, sed, awkNo

The row limit is worth knowing before you find it. A worksheet stops at 1,048,576 rows and 16,384 columns, and a load that exceeds it does not always announce the truncation loudly. CSV has no such ceiling — a 40 million line file is unremarkable, and every line of it can be processed without holding the rest in memory.

The diff row is the one that decides it for anything version-controlled. A CSV committed to git shows exactly which rows changed, reviewable in a pull request. A workbook shows "binary files differ", which means the review has to happen somewhere else, by someone opening both copies.

Choosing, in three questions

  1. Will a person open it, or a program? A program wants CSV — no ambiguity about which sheet, no formatting to skip past, and it streams. A person on a laptop wants a workbook, because it opens with the columns already the right width and nothing looks like it needs fixing.
  2. Does it contain identifiers? Account numbers, SIM identifiers, postal codes, anything zero-padded — these are the values a spreadsheet damages on open. Send CSV, and if the recipient will definitely open it in Excel, send a workbook with those columns already typed as text so the guess never happens.
  3. Does it need review, history, or appending? Then CSV, without hesitation. Diffs, tail -f, appending a day's rows to yesterday's file — none of that exists for a workbook.

The short answer most teams land on: CSV as the interchange and archive format, workbooks generated on demand when a human has asked for one. Keeping the workbook as the source of truth is what creates the "which copy is current" problem six months later.

Moving between them without losing anything

Both directions are safe as long as whatever does the conversion treats cells as text rather than guessing. The tools here do — a 19-digit identifier survives the trip in either direction, which is the whole reason they exist.

Excel to CSV Pull a sheet out of a workbook as text, with long identifiers left exactly as they were stored. CSV to Table Detects the delimiter, and shows the parse a strict reader sees rather than a spreadsheet's interpretation. JSON to Excel Build a workbook from records, with oversized numbers written as text cells so Excel cannot re-round them.

If a file will not parse at all — ragged rows, an unterminated quote halfway down — CSV Fixer reports the line rather than failing the whole document, and CSV Formatter will settle the quoting and line endings once so the next reader has nothing to guess.

The one-paragraph version

CSV loses meaning — it cannot tell you the delimiter, the encoding, or what type a column is, so both ends have to agree in advance. Excel loses data — it decides those things for you on open, and the decision overwrites what was there. The first loss is annoying and recoverable, because the bytes are still correct and you only need to be told how to read them. The second is silent and permanent.

  • Send CSV to machines, workbooks to people.
  • Any column of identifiers is a reason to prefer CSV, or to type the column as text before anyone opens it.
  • Anything under version control is CSV. A workbook cannot be reviewed in a diff.
  • Gzipped CSV is a fraction of the size of the equivalent workbook — for transfer and archive it is not a close call.
  • A spreadsheet has not corrupted the file until someone saves. The CSV on disk is still right up to that point.
The failure cases in one file Every value from this article in one CSV. Open it in a spreadsheet and in CSV to Table side by side — the two will disagree, which is the entire point.