LineBreaks

CRLF vs LF

Two bytes versus one, and forty years of argument about which. This page is the reference: what the Unicode Standard says about platform conventions, what eleven tools on one Windows machine actually wrote to disk when asked for two lines, and what git does to your files under each core.autocrlf setting — measured, not described.

Nothing but the bytes between lines changes.

5 lines · 31 characters · 5 wordsCRLF ×1LF ×1CR ×1NEL ×1
5 lines · 30 characters-1 characters

One file, five lines, four different line endings — CRLF, LF, CR and NEL. This is what a file looks like after two people edit it on two platforms.

The two characters

LF is U+000A, one byte, 0A. CR is U+000D, one byte, 0D. CRLF is the two of them in that order, 0D 0A — a sequence, not a character, which is why it has no code point of its own. All three mean "start a new line"; the disagreement is purely about spelling.

The Unicode Standard uses the term newline function for the abstract operation and tabulates which characters represent it on which platform. Quoted from section 5.8 of the core specification, Table 5-2, read directly on unicode.org:

The Unicode Standard 16.0.0, core specification, §5.8, Table 5-2. Reproduced as published; the third column is this page's commentary, not Unicode's.
PlatformNewline functionBytes
MacOS 9.x and earlier CR 0D
MacOS X LF 0A
Unix LF 0A
Windows CRLF 0D 0A
EBCDIC-based OS NEL C2 85 (as UTF-8)

A note on where CRLF came from

The usual explanation is mechanical: a carriage return moved the print head back to the left margin and a line feed advanced the paper by one line, so a teleprinter needed both instructions and the two-character sequence outlived the machines that required it.

That account is not sourced on this page. The Unicode newline section, which is the authority that was opened, tabulates the platform conventions and says nothing about where they came from, and we did not open a primary document that does. Take it as the received explanation, not as something this site checked.

What eleven tools on this machine actually wrote

Every writer below was asked for the same thing: the two lines alpha and beta. The bytes column is what came back off the disk. Nothing is inferred — each row was run 5 times and produced byte-identical output every time.

Measured on one Windows machine by scripts/measure.mjs, which writes the data module this table renders — no figure here was typed by hand.
ToolCommandBytes on diskEndings
Node 22 · fs.writeFileSync
No translation layer: the bytes you pass are the bytes on disk.
fs.writeFileSync(f, 'alpha\nbeta') 61 6C 70 68 61 0A 62 65 74 61
10 bytes, no trailing terminator
LF×1
Node 22 · join(os.EOL)
os.EOL is the platform constant; on Windows it is CRLF.
fs.writeFileSync(f, ['alpha','beta'].join(os.EOL)) 61 6C 70 68 61 0D 0A 62 65 74 61
11 bytes, no trailing terminator
CRLF×1
Python 3.11 · text mode (default)
Default newline=None translates every \n to os.linesep on write.
open(f,'w').write('alpha\nbeta') 61 6C 70 68 61 0D 0A 62 65 74 61
11 bytes, no trailing terminator
CRLF×1
Python 3.11 · newline=""
Translation disabled — the documented way to keep LF on Windows.
open(f,'w',newline='').write('alpha\nbeta') 61 6C 70 68 61 0A 62 65 74 61
10 bytes, no trailing terminator
LF×1
Python 3.11 · binary mode
Binary mode never translates, on any platform.
open(f,'wb').write(b'alpha\nbeta') 61 6C 70 68 61 0A 62 65 74 61
10 bytes, no trailing terminator
LF×1
PowerShell 7 · Set-Content (array)
Each array element becomes a line, and a trailing terminator is added.
Set-Content f -Value @('alpha','beta') 61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
13 bytes, ends with a terminator
CRLF×2
PowerShell 7 · Set-Content (one string with LF)
A single string is written as-is — the LF inside it is NOT converted.
Set-Content f -Value "alpha`nbeta" 61 6C 70 68 61 0A 62 65 74 61 0D 0A
12 bytes, ends with a terminator
CRLF×1 + LF×1
PowerShell 7 · Out-File
The pipeline formats each object on its own line before writing.
@('alpha','beta') | Out-File f 61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
13 bytes, ends with a terminator
CRLF×2
PowerShell 7 · > redirection
Redirection is Out-File under a different spelling.
@('alpha','beta') > f 61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
13 bytes, ends with a terminator
CRLF×2
cmd.exe · echo redirection
The classic batch idiom, still the default in .bat files.
(echo alpha& echo beta) > f 61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
13 bytes, ends with a terminator
CRLF×2
Git Bash · printf
A POSIX shell on Windows writes POSIX endings.
printf 'alpha\nbeta' > f 61 6C 70 68 61 0A 62 65 74 61
10 bytes, no trailing terminator
LF×1

Three things in that table are worth more than the headline. 4 of the 11 writers produced plain LF on Windows, which is the point: the line ending is a property of the program, not of the operating system. Python's default text mode translated every \n into CRLF while its binary mode and its newline="" mode did not — one language, three behaviours, all in the same interpreter.

And one command produced a mixed file. Set-Content f -Value "alpha`nbeta" wrote 61 6C 70 68 61 0A 62 65 74 61 0D 0A — CRLF×1 + LF×1. The LF inside the string was passed through untouched and a CRLF terminator was appended after it, so a single command with no obvious ambiguity produced a file with two different line endings in it. Mixed files are not exotic; they are one keystroke away.

What git does, measured

Git's documentation describes core.autocrlf like this — quoted from the git-config documentation shipped inside Git for Windows 2.51.0.windows.1, read directly from the installed distribution:

Setting this variable to "true" is the same as setting the text attribute to "auto" on all files and core.eol to "crlf". Set to true if you want to have CRLF line endings in your working directory and the repository has LF line endings. This variable can be set to input, in which case no output conversion is performed.

A description of a conversion is not the same thing as watching one happen, so the table below is a measurement. For each setting, a throwaway repository was created, one file authored with LF and one with CRLF, both staged and committed; then the bytes git stored in the blob were read back with git cat-file, the working copies deleted, and the files checked out again.

Measured with git version 2.51.0.windows.1 on Windows_NT 10.0.26200 (x64), 2026-08-14. "Round trip" asks whether the file you get back is byte-identical to the file you wrote.
core.autocrlf You wrote Stored in the commit Checked back out Round trip
false LF
61 6C 70 68 61 0A 62 65 74 61 0A
LF×2
61 6C 70 68 61 0A 62 65 74 61 0A
LF×2
61 6C 70 68 61 0A 62 65 74 61 0A
identical
false CRLF
61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
CRLF×2
61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
CRLF×2
61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
identical
true LF
61 6C 70 68 61 0A 62 65 74 61 0A
LF×2
61 6C 70 68 61 0A 62 65 74 61 0A
CRLF×2
61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
changed
true CRLF
61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
LF×2
61 6C 70 68 61 0A 62 65 74 61 0A
CRLF×2
61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
identical
input LF
61 6C 70 68 61 0A 62 65 74 61 0A
LF×2
61 6C 70 68 61 0A 62 65 74 61 0A
LF×2
61 6C 70 68 61 0A 62 65 74 61 0A
identical
input CRLF
61 6C 70 68 61 0D 0A 62 65 74 61 0D 0A
LF×2
61 6C 70 68 61 0A 62 65 74 61 0A
LF×2
61 6C 70 68 61 0A 62 65 74 61 0A
changed

Read the round-trip column first. 2 of the 6 combinations hand you back a different file from the one you committed, and both are working exactly as documented. Neither is a bug. With autocrlf=true, a file you authored with LF comes back as CRLF — git normalised it to LF in the object database and then converted it on the way out, which is the whole design. With autocrlf=input, a file you authored with CRLF comes back as LF, because input normalises on the way in and performs no conversion on the way out.

With autocrlf=false, both files round-trip exactly: git stores the bytes you gave it and returns them. That is why false plus a .gitattributes file is the configuration most projects settle on — it moves the decision from each developer's machine into the repository, where it can be reviewed.

Git also warns about the failure mode that measurement cannot show, because it needs a file that is already mixed:

CRLF conversion bears a slight chance of corrupting data. When it is enabled, Git will convert CRLF to LF during commit and LF to CRLF during checkout. A file that contains a mixture of LF and CRLF before the commit cannot be recreated by Git.

That is the practical reason to care. A mixed file — of which this page has already shown one being produced by a single innocuous command — cannot survive normalisation intact, because normalisation has no way to know which of your CRLFs were deliberate.

Which to choose, in practice

  • Source code. LF in the repository, always. Set core.autocrlf=false and commit a .gitattributes with * text=auto eol=lf so the decision travels with the project.
  • Data files consumed by scripts. LF. A trailing CR on the end of every field is exactly the kind of difference that makes a parser work on one machine and fail on another.
  • Files that must satisfy a protocol. CRLF, because the protocol says so — HTTP/1.1 header lines under RFC 9112, CSV records under RFC 4180.
  • Plain text for Windows users to open. CRLF is the safer default; some long-standing Windows tools still render an LF-only file as one long line.
  • Anything you are about to diff. Whatever the other side uses. A whole-file diff caused by an ending change hides the one line you actually edited.

Fixing a file you already have

Paste it into the tool at the top of this page, which opens in ending-conversion mode with LF selected, and nothing but the bytes between lines will change. The byte-level table for every input and target — including what happens to NEL and U+2028 on the way — is on the conversion page. If what you actually have is a file that looks double-spaced, check for orphan carriage returns before deleting anything: that is a line-ending problem, and removing the blank lines would destroy the file it was meant to fix.

The questions CRLF keeps raising

What is the difference between CRLF and LF?

LF is one byte, 0A. CRLF is two, 0D 0A. Both mean "new line". Which one a file uses depends on what wrote it, and the Unicode Standard tabulates the platform conventions — Windows CRLF, Unix and current macOS LF, macOS 9 and earlier CR, EBCDIC systems NEL.

Which one should I use?

LF, unless something specifically demands otherwise. It is what Unix, macOS, the web, JSON and virtually every build tool expect, and it is one byte shorter per line. The exceptions are real though: HTTP/1.1 terminates every header line with CRLF and RFC 4180 separates CSV records with it, and some Windows programs still expect it in plain text files.

Why does my file show ^M at the end of every line?

You are looking at a CRLF file in a tool that expects LF. The ^M is the carriage return rendered as a caret escape. Nothing is broken — the file simply has Windows line endings. Convert them and the markers go.

What does core.autocrlf actually do?

Git's own documentation says: “Setting this variable to "true" is the same as setting the text attribute to "auto" on all files and core.eol to "crlf". Set to true if you want to have CRLF line endings in your working directory and the repository has LF line endings. This variable can be set to input, in which case no output conversion is performed.” We ran all three settings in a throwaway repository and recorded the bytes, because a description of a conversion is not the same thing as watching one happen. The table is below.

Why did git say my whole file changed when I only edited one line?

Because the line endings changed for every line — usually because an editor rewrote the file in the other convention. That is the same class of problem as core.autocrlf conversion, and git warns about it: “CRLF conversion bears a slight chance of corrupting data. When it is enabled, Git will convert CRLF to LF during commit and LF to CRLF during checkout. A file that contains a mixture of LF and CRLF before the commit cannot be recreated by Git.”

Is a mixed file a real thing?

Yes, and it is easier to create than you would think. One of the 11 writers measured on this page produces a mixed file from a single command — a PowerShell Set-Content with a string containing an LF writes the LF unchanged and then adds a CRLF terminator, so the file has one of each.

Does CRLF actually cost anything?

One byte per line, which is nothing for a document and measurable for a large log. The arithmetic is on the conversion page, worked through rather than rounded to a percentage.

Once you know which ending you have

  • the all-in-one line break remover — conversion is one mode of six, and the hub prints what each of the other five would do to your CRLFs — including the three that leave them exactly alone.
  • Convert line endings — the byte-level conversion table for LF, CRLF and CR, with the round-trip check.
  • Remove line breaks in Excel and CSV — why a CSV has two kinds of line break that look identical, and how to strip one without destroying the other.
  • Remove blank lines — eleven candidate lines and three definitions of "blank" that disagree about nine of them.
  • Word Counter — the other half of the tidy-up job: word, character and reading-time counts once the text is one paragraph again.
  • Case Converter — text pasted out of a PDF often arrives SHOUTING as well as broken — this fixes the capitalisation under four style guides.