LF vs CRLF Line Endings
A practical guide to LF and CRLF line endings, their history, differences, Git behavior, programming languages, cross-platform issues, and reliable conversion strategies.
A line ending is the character or character sequence used to mark the end of one line of text and the beginning of the next. The two most common conventions in modern software development are LF and CRLF.
LF uses a single line feed character, U+000A. CRLF uses two characters in sequence: carriage return, U+000D, followed by line feed, U+000A. Although both normally appear as the same visible line break, they are different byte sequences.
The difference is easy to ignore until a file moves between operating systems, a Git configuration changes, a script expects a particular line ending, or a text-processing tool interprets CR and LF differently.
What Is LF?
LF stands for Line Feed. It is represented by the Unicode code point U+000A and is commonly written as \n in programming languages.
Line one\nLine twoIn a UTF-8 text file, LF is represented by the single byte 0A. A file using LF therefore needs only one control character for each line break.
LF line ending:
0ALF is the standard line-ending convention on Unix-like systems, including Linux and macOS. It is also extremely common in modern development tools, source repositories, configuration files, and network-oriented text formats.
What Is CRLF?
CRLF stands for Carriage Return followed by Line Feed. It consists of two control characters: U+000D and U+000A.
Line one\r\nLine twoIn a UTF-8 file, CRLF is represented by two bytes: 0D 0A.
CRLF line ending:
0D 0ACRLF is traditionally associated with Windows. Many Windows applications and tools continue to use it, although modern Windows development environments can work perfectly well with LF.
LF vs CRLF at a Glance
| Property | LF | CRLF |
|---|---|---|
| Name | Line Feed | Carriage Return + Line Feed |
| Unicode sequence | U+000A | U+000D U+000A |
| Common escape | \n | \r\n |
| UTF-8 bytes | 0A | 0D 0A |
| Traditional platform | Unix/Linux/macOS | Windows |
| Bytes per line ending | 1 | 2 |
| Common in modern source code | Very common | Very common |
Why Are There Different Line Endings?
The difference comes from the history of text terminals and operating systems. The names carriage return and line feed originate from mechanical typewriters and teleprinters.
A carriage return moved the printing position back to the beginning of a line. A line feed moved the paper or print position down by one line. Older systems could therefore represent the actions of starting a new line using two separate control operations.
Unix adopted LF as its conventional newline representation. DOS and later Windows inherited the CRLF convention. Other systems historically used additional conventions, including CR alone.
What Is CR?
CR stands for Carriage Return and is represented by U+000D. In programming languages it is commonly written as \r.
CR by itself is a historical line-ending convention associated with older systems such as classic Mac OS. Modern macOS uses LF, not CR.
| Sequence | Name | Common historical association |
|---|---|---|
| LF | Line Feed | Unix and Unix-like systems |
| CRLF | Carriage Return + Line Feed | DOS and Windows |
| CR | Carriage Return | Classic Mac OS |
Most modern development work therefore encounters LF and CRLF far more often than standalone CR.
LF and CRLF Look the Same
The most confusing aspect of line endings is that they normally have no visible symbol. An editor can display two files with identical text even though their underlying bytes are different.
LF:
first line
second line
CRLF:
first line
second lineVisually, there is no obvious difference. Internally, however, the line break between first line and second line is represented by either 0A or 0D 0A.
How Line Endings Are Stored in UTF-8
Line endings are independent of UTF-8 itself. UTF-8 encodes the control characters used by the selected line-ending convention.
LF:
0A
CRLF:
0D 0ABecause both U+000A and U+000D are part of the ASCII range, their UTF-8 representation is identical to their one-byte ASCII representation.
LF vs CRLF in Git
Git is one of the places where developers most frequently encounter line-ending differences. Git can be configured to normalize line endings when files move between the working tree and repository.
This is useful because a project can maintain a consistent representation in the repository while allowing developers on different operating systems to use their preferred working-tree format.
The core.autocrlf Setting
Git's core.autocrlf setting controls automatic conversion between CRLF in the working tree and LF in the repository in common configurations.
| Setting | Typical behavior |
|---|---|
| core.autocrlf=true | Convert LF to CRLF when checking out and CRLF to LF when committing |
| core.autocrlf=input | Convert CRLF to LF when committing, but do not convert LF to CRLF on checkout |
| core.autocrlf=false | Do not perform automatic line-ending conversion |
The exact behavior also depends on Git attributes and repository configuration. The important point is that Git can distinguish the content stored in the repository from the representation used in a developer's working tree.
Using .gitattributes
For teams, .gitattributes provides a more explicit way to define how Git should treat particular files. A common configuration is to normalize text files to LF in the repository.
* text=auto
*.js text eol=lf
*.ts text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.json text eol=lfThis tells Git that these files are text and should use LF in the working tree. The exact rules should be adapted to the project's files and requirements.
Why Line Ending Changes Create Huge Git Diffs
A line-ending conversion can change every line in a file from Git's byte-level perspective. If a file containing hundreds of lines changes from LF to CRLF, Git may report the entire file as modified even though the visible text did not change.
This creates noisy diffs and makes real code changes difficult to review.
Original:
line 1\n
line 2\n
line 3\n
After CRLF conversion:
line 1\r\n
line 2\r\n
line 3\r\nIf this happens accidentally, it is usually better to normalize the file before committing rather than reviewing or merging a diff that contains an unnecessary line-ending change.
LF vs CRLF in VS Code
Visual Studio Code can detect the line-ending style of the current file and display it in the status bar. You can also change the line-ending style when editing a file.
This makes it easy to inspect whether a file currently uses LF or CRLF without opening it in a hexadecimal editor.
For a project, it is often better to configure the desired convention consistently through project settings and Git attributes rather than manually changing individual files whenever a problem appears.
Configuring the Default Line Ending in VS Code
VS Code supports a files.eol setting that can define the default end-of-line sequence for files.
{
"files.eol": "\n"
}Using \n selects LF. A project can place this setting in its workspace configuration when the team wants a predictable editor default.
LF vs CRLF in JavaScript
JavaScript source code can use either LF or CRLF. The JavaScript language itself does not require developers to use one particular physical line-ending convention for source files.
JavaScript string literals are a separate matter. Within a string, \n represents a line feed character, while \r represents carriage return. A string containing \r\n therefore contains two characters.
const lf = "line one\nline two";
const crlf = "line one\r\nline two";
console.log(lf === crlf);
// falseThis distinction becomes important when reading files, processing HTTP payloads, parsing text protocols, or comparing strings generated by different systems.
Reading Text Files in JavaScript
When JavaScript reads a text file, the application may receive the original line-ending characters depending on the API and processing performed afterward.
const lines = text.split(/\r?\n/);The \r?\n pattern accepts both LF and CRLF. This is a common technique when an application needs to process text originating from different platforms.
Normalizing Line Endings in JavaScript
If an application needs one consistent internal representation, it can normalize line endings before processing the text.
function normalizeLineEndings(text) {
return text.replace(/\r\n|\r/g, "\n");
}
const normalized = normalizeLineEndings(input);This converts CRLF and standalone CR to LF. Once normalized, later string processing can assume that every line break is represented by \n.
Converting LF to CRLF
The reverse conversion can be useful when generating files for software that specifically expects CRLF.
function toCrLf(text) {
return text.replace(/\r?\n/g, "\r\n");
}It is important to normalize existing CRLF first. Simply replacing every \n with \r\n can turn an existing CRLF sequence into \r\r\n.
Why \r\n Can Become \r\r\n
A common line-ending conversion bug occurs when a program performs a simple string replacement from \n to \r\n on text that already contains CRLF.
const input = "one\r\ntwo";
const broken = input.replace(/\n/g, "\r\n");
console.log(JSON.stringify(broken));
// "one\r\r\ntwo"The existing carriage return is preserved and a second carriage return is inserted before the line feed. Correct conversion should first recognize existing line-ending forms.
Line Ending Conversion With a Regular Expression
A robust normalization pattern can recognize CRLF, CR, and LF separately.
const normalized = text.replace(/\r\n|\r|\n/g, "\n");The order matters. CRLF should be matched before standalone CR and LF so that the two-character sequence is treated as one line ending.
LF vs CRLF in Python
Python provides several mechanisms for working with text files and line endings. When text is opened in normal text mode, Python can perform universal newline processing depending on the newline parameter.
with open("example.txt", "r", newline=None, encoding="utf-8") as file:
text = file.read()With universal newline handling, common newline conventions can be translated into \n when reading text. When exact line-ending preservation is important, the newline parameter can be configured differently.
LF vs CRLF in Shell Scripts
One of the most common practical problems occurs when a shell script created or edited on Windows is executed in a Linux environment.
A shell script with CRLF line endings can contain a carriage return at the end of each line. Linux tools may then interpret that hidden \r as part of the command or interpreter path.
#!/bin/sh\r
echo "Hello"\rThe script may look normal in an editor but fail when executed on Linux. Errors mentioning unexpected characters or an interpreter path containing a strange suffix are often a clue that CRLF was used where LF was expected.
CRLF Problems in Docker and Linux Environments
The same issue can appear in Docker-based development. A project may be edited on Windows and then executed inside a Linux container. Source files are usually handled correctly by language runtimes, but shell scripts and other line-sensitive files can expose the difference.
This is one reason cross-platform projects often standardize source and configuration files on LF, especially when the repository is built and executed in Linux-based CI or container environments.
Line Endings in Configuration Files
Most configuration formats can tolerate either LF or CRLF when parsed by a standards-compliant parser. Problems arise when a custom parser, shell command, or simplistic text-processing script assumes a specific representation.
Configuration files are especially sensitive when they are processed line by line using low-level string operations. An unexpected carriage return can remain attached to the last token on a line.
Line Endings and Regular Expressions
Regular expressions can behave differently around CRLF because CRLF consists of two characters rather than one. A pattern that expects \n may still find the LF character, but the preceding \r can affect surrounding matches.
const text = "one\r\ntwo";
console.log(text.split("\n"));
// ["one\r", "two"]The carriage return remains attached to the first line because split("\n") removes only the LF. A cross-platform parser can instead use \r?\n when the input is expected to contain LF or CRLF.
Line Endings and Find & Replace
Find-and-replace operations can be useful for fixing line endings, but they can also create problems if the replacement pattern does not account for existing CRLF sequences.
A safer workflow is to identify the current line-ending style first, then convert the complete sequence rather than treating CR and LF as unrelated characters.
Line Endings and Whitespace
Line endings are technically control characters rather than ordinary visible whitespace, although text-processing tools often group them with whitespace-related characters.
A whitespace visualizer can make tabs, spaces, CR, and LF easier to distinguish. This is especially useful when a file appears to contain blank or malformed lines.
Line Endings and Text Cleaning
Text cleaning often includes line-ending normalization because consistent line breaks simplify later processing. However, line-ending normalization should not be confused with removing meaningful content.
For example, converting CRLF to LF preserves the number and position of logical line breaks. It changes only their representation.
How to Detect the Line Ending of a File
There are several ways to determine whether a file uses LF or CRLF.
- Check the editor's line-ending indicator.
- Inspect the file with a hexadecimal or byte viewer.
- Search for carriage return and line feed characters explicitly.
- Use a line-ending detection tool.
- Inspect Git diffs when a file appears to have changed completely.
- Read the file programmatically and check for \r\n versus \n.
function detectLineEnding(text) {
if (text.includes("\r\n")) {
return "CRLF";
}
if (text.includes("\n")) {
return "LF";
}
if (text.includes("\r")) {
return "CR";
}
return "None";
}A simple detector should be treated as a diagnostic helper. Real files can contain mixed line endings, so a more complete implementation may need to count each type rather than returning the first one found.
Detecting Mixed Line Endings
A file does not have to use the same line-ending sequence everywhere. It is possible for one part of a file to use LF while another uses CRLF.
function countLineEndings(text) {
const crlf = (text.match(/\r\n/g) || []).length;
const lf = (text.match(/(?<!\r)\n/g) || []).length;
const cr = (text.match(/\r(?!\n)/g) || []).length;
return { crlf, lf, cr };
}Mixed line endings often appear after files have been edited by multiple tools or after partial conversions. They are worth normalizing because different parts of the same file can otherwise behave differently in scripts and diffs.
How to Convert CRLF to LF
CRLF can be converted to LF by replacing each CRLF sequence with a single LF.
const lfText = text.replace(/\r\n/g, "\n");If the input may also contain standalone CR, a more comprehensive normalization can convert all common forms to LF.
const normalized = text.replace(/\r\n|\r/g, "\n");How to Convert LF to CRLF
To convert LF text to CRLF safely, first normalize the input and then replace each logical line break with CRLF.
const crlfText = text
.replace(/\r\n|\r/g, "\n")
.replace(/\n/g, "\r\n");The first step prevents existing CRLF sequences from becoming CRCRLF.
Should You Use LF or CRLF?
There is no universal requirement that every project use the same line-ending convention. The appropriate choice depends on the project, target platforms, tools, and repository policy.
For modern cross-platform software development, LF is commonly convenient because it is the native convention of Unix-like environments and is widely supported by editors and development tools.
CRLF remains perfectly valid and is still common in Windows-oriented environments. The important issue is consistency rather than treating one convention as universally correct.
When CRLF Makes Sense
- A project explicitly requires Windows-style line endings.
- A legacy tool expects CRLF.
- A file format or external system specifies CRLF.
- The project has an established Windows-oriented convention.
- A generated file must match another system's exact byte representation.
When LF Makes Sense
- The project targets Linux or Unix-like environments.
- Source files are shared across multiple operating systems.
- CI and deployment environments are Linux-based.
- The project wants one simple cross-platform repository convention.
- Development tooling already expects LF.
A Practical Cross-Platform Strategy
For a cross-platform development project, a practical strategy is to choose one repository representation, configure Git explicitly, and configure editors to follow the same convention.
- Choose LF or CRLF as the project's intended convention.
- Document the choice when necessary.
- Use .gitattributes for repository-level behavior.
- Configure editor defaults for the project.
- Avoid committing files with mixed line endings.
- Check generated files separately if external tools require a different format.
Line Endings in CI/CD
Continuous integration systems can run on a different operating system from the developer's machine. A repository that is edited on Windows may be built inside a Linux environment, making line-ending consistency important.
This is particularly relevant for shell scripts, generated configuration files, Docker entrypoint scripts, and other files that are executed rather than simply parsed by a tolerant library.
Line Endings and Containers
Containers commonly use Linux userland even when they are developed from a Windows host. A file can therefore cross an operating-system boundary during the build or deployment process.
Keeping repository files normalized to LF can reduce surprises when those files are executed inside Linux containers. This is especially useful for shell scripts and configuration consumed by command-line tools.
Line Endings and Dockerfiles
Dockerfiles generally tolerate normal text line endings, but scripts copied into images are more likely to expose CRLF problems. A shell script with Windows line endings can fail when executed by a Linux shell.
If a containerized application reports that a script cannot be executed even though the file exists and its permissions appear correct, checking the line endings is a useful diagnostic step.
Line Endings and Shell Commands
Command-line tools can expose line-ending differences more clearly than graphical editors. Utilities that process files line by line may retain a trailing carriage return when given CRLF input.
For example, a shell pipeline that reads each line and passes it directly to another command can accidentally include \r in the argument when running against a CRLF file.
Line Endings in Network Protocols
Some network protocols explicitly define CRLF as part of their syntax. HTTP/1.1 is a notable example: protocol elements such as header lines use CRLF delimiters.
This is an important distinction. Developers should not globally convert every CRLF to LF without considering whether the bytes belong to a protocol whose specification requires CRLF.
Line Endings Are Not the Same as BOM
BOM and line endings are separate file properties. A BOM appears at the beginning of a Unicode text stream, while line endings occur between lines.
| Property | BOM | Line ending |
|---|---|---|
| Purpose | Encoding signature / byte order | Separates lines |
| Typical location | Beginning of file | Between lines |
| UTF-8 example | EF BB BF | 0A or 0D 0A |
| Common issue | Unexpected invisible character | Noisy diffs or parser errors |
A file can therefore be UTF-8 with BOM and use LF, or UTF-8 without BOM and use CRLF. These choices are independent.
Line Endings and File Size
CRLF uses one more byte than LF for every line ending in an ASCII-compatible encoding such as UTF-8. A file with many lines can therefore be slightly larger when stored with CRLF.
For example, a file with 10,000 line endings contains approximately 10,000 more bytes when using CRLF instead of LF, assuming all other content is identical.
Line Endings and Hashes
Changing LF to CRLF changes the underlying bytes, so it also changes any hash calculated from the raw file contents.
LF:
48 69 0A 42 79 65
CRLF:
48 69 0D 0A 42 79 65This matters for checksums, content-addressed storage, cache keys, signatures, reproducible builds, and any system where exact byte identity matters.
Common Line Ending Problems
- A Git diff shows every line as changed after editing a file.
- A shell script works on Windows but fails inside Linux.
- A parser leaves \r at the end of every line.
- Splitting text with \n produces strings ending in \r.
- A conversion creates \r\r\n sequences.
- Files contain mixed LF and CRLF line endings.
- Different developers repeatedly convert the same files back and forth.
- Generated files differ byte-for-byte even though their visible text is identical.
Troubleshooting Line Ending Problems
When line-ending behavior is suspicious, start by determining exactly what is stored in the file rather than changing multiple tools at once.
- Inspect the file's line-ending indicator in the editor.
- Check whether the file contains LF, CRLF, or mixed endings.
- Inspect the Git diff for an all-lines-changed pattern.
- Check .gitattributes and Git configuration.
- Check editor settings such as files.eol.
- Inspect scripts that process the file line by line.
- Normalize the file once and commit the intentional change separately.
- Verify the result in the target operating system or CI environment.
A Safe Normalization Function
For ordinary text input, a useful normalization function is one that converts all supported line-ending styles to a single internal representation.
function normalizeToLf(text) {
return text.replace(/\r\n|\r/g, "\n");
}
const text = "one\r\ntwo\rthree\nfour";
const normalized = normalizeToLf(text);
console.log(JSON.stringify(normalized));
// "one\ntwo\nthree\nfour"Once the text is normalized to LF, the application can process it consistently. If a specific output format requires CRLF, conversion can happen at the final output step.
Best Practices
- Choose a line-ending convention deliberately.
- Keep repository files consistent.
- Use .gitattributes when a project needs explicit Git behavior.
- Configure editor defaults to match the project.
- Normalize external text before processing when the input format allows it.
- Do not convert protocol data or binary data blindly.
- Check shell scripts when moving files from Windows to Linux.
- Watch for mixed line endings in generated or copied files.
- Avoid committing accidental whole-file line-ending changes.
- Treat line endings as bytes when exact file identity matters.
Frequently Asked Questions
What is the difference between LF and CRLF?
LF is a single line feed character, U+000A. CRLF consists of carriage return followed by line feed, U+000D U+000A. In UTF-8, they are represented by 0A and 0D 0A respectively.
Which line ending does Windows use?
Windows traditionally uses CRLF. Modern Windows editors and development tools can also work with LF, so the operating system does not prevent a project from using LF.
Which line ending does Linux use?
Linux traditionally uses LF. Unix-like systems generally use LF as their standard line-ending convention.
Which is better, LF or CRLF?
Neither is universally better. The appropriate choice depends on the project's requirements, target environments, tools, and repository policy. Consistency is usually more important than the choice itself.
Why does Git show every line as changed?
A common cause is a line-ending conversion. If a file changes from LF to CRLF or vice versa, Git can see the change at the byte level even though the visible text remains the same.
Why does a shell script fail on Linux after being edited on Windows?
The script may contain CRLF line endings. The Linux shell can interpret the carriage return character as part of the line or interpreter path. Converting the script to LF commonly resolves this type of problem.
How do I convert CRLF to LF?
Replace CRLF sequences with LF, or use a line-ending conversion tool or editor setting. For mixed input, normalize CRLF and standalone CR to LF rather than replacing only individual LF characters.
Can a file contain both LF and CRLF?
Yes. A text file can contain mixed line endings, often because it was edited by multiple tools. Mixed endings can be detected and normalized when the file format permits it.
Helpful Text and Encoding Tools
Line-ending problems are often easiest to diagnose when invisible characters are made explicit. Line ending converters can switch between LF, CRLF, and other newline conventions, while whitespace visualizers can reveal spaces, tabs, carriage returns, line feeds, and other hidden characters. Text cleaners can normalize unwanted whitespace and control characters, and find-and-replace tools can apply targeted transformations.
Duplicate line removers can help clean repeated lines after text normalization, while Unicode inspectors can distinguish line endings from other invisible characters. Together, these tools make it easier to inspect, normalize, and compare text without relying only on how it appears on screen.
Conclusion
LF and CRLF are two different ways to represent the end of a line. LF uses U+000A, while CRLF uses U+000D followed by U+000A. They usually look identical in an editor, but they produce different byte sequences and can therefore affect Git diffs, scripts, parsers, generated files, hashes, and cross-platform workflows.
For modern cross-platform development, LF is a common repository convention, particularly when software is built or executed in Unix-like environments. CRLF remains valid and widely used, especially in Windows-oriented workflows. The key is to establish a predictable convention and configure Git and development tools accordingly.
When a line-ending problem appears, inspect the actual file rather than relying on how it looks. Determine whether the file uses LF, CRLF, or mixed endings, check repository and editor configuration, and normalize the file only when the target format allows it.