Table of Contents
Back to Blog

JavaScript Deep Dive

How to Count Lines in JavaScript: 6 Methods with Performance Benchmarks

Count lines in JavaScript strings, files, Node.js streams, and the browser. Includes real performance benchmarks, edge case handling, and a decision guide for every scenario.

JavaScript ES2020+Node.js 20 LTSBrowser File API
Published: May 12, 2026Updated: May 12, 202618 min readAuthor: Line Counter Editorial Team
JavaScriptNode.jsBrowser APIsTutorialPerformance

You paste 50,000 lines of log output into a string variable. You call .split('\n').length. You get 50,001.

Why?

That off-by-one result is the most common bug when developers count lines in JavaScript. A trailing newline creates an extra empty array item, Windows files use \r\n instead of just \n, and large files can crash a process if you read everything into memory before counting. The right answer depends on where the text lives and how large it is.

The practical challenge is that "count lines in JavaScript" can mean at least six different jobs. You might count lines in JavaScript strings, uploaded browser files, Node.js files, streams, textarea input, or source code. Each case has a different failure mode.

This guide covers six practical ways to count lines in JavaScript:

If you came from Python, the edge cases are similar but the APIs are different. The companion Python line counting guide covers splitlines(), file iteration, and wc -l comparisons in detail.

30-Second Decision Guide

Use this guide before choosing code. The fastest way to count lines in JavaScript is not always the best way; the best method is the one that matches your input size and runtime.

Decision Tree

Here is the short version: use string counting for values already in memory, browser streams for large uploads, Node.js streams for server-side files, and a purpose-built code counter when comments and blank lines matter. If you only need to count lines in JavaScript examples once, the browser-based Line Counter tool is the no-code path.

Method 1: Count Lines in a JavaScript String

Use this method when

Your text is already a JavaScript string and is small enough to keep in memory.

Avoid this method when

You are reading a large file from disk or an uploaded File object. Stream the file instead.

The simplest answer to JavaScript count lines in string is str.split('\n').length.

// Basic method
function countLines(str) {
  return str.split('\n').length;
}

console.log(countLines("line1\nline2")); // 2

That is fine for a quick demo, but it is not always the result users expect.

function countLines(str) {
  return str.split('\n').length;
}

console.log(countLines("line1\nline2\n")); // 3, not 2

The split operation sees two newline separators and produces three array items: "line1", "line2", and "". That empty final string is the off-by-one bug.

Use this corrected version when a trailing newline should not count as an extra logical line:

function countLinesAccurate(str) {
  if (str.length === 0) return 0;

  const lines = str.split('\n');
  return lines[lines.length - 1] === '' ? lines.length - 1 : lines.length;
}

console.log(countLinesAccurate("line1\nline2\n")); // 2

For cross-platform input, count newline sequences directly:

function countLinesCrossPlatform(str) {
  if (str.length === 0) return 0;

  const newlineMatches = str.match(/\r\n|\r|\n/g) || [];
  const endsWithNewline = /\r\n|\r|\n$/.test(str);

  return newlineMatches.length + (endsWithNewline ? 0 : 1);
}

console.log(countLinesCrossPlatform("a\r\nb")); // 2
console.log(countLinesCrossPlatform("a\nb\n")); // 2
console.log(countLinesCrossPlatform("")); // 0

If you only need to support Unix-style \n, this concise version is common:

const countLines = (str) => (str ? (str.match(/\n/g) || []).length + 1 : 0);

That concise version is useful, but it does not correct the trailing-newline interpretation. For user-facing counters, prefer the explicit countLinesCrossPlatform version.

Inputsplit('\n').lengthCorrected countNotes
""10Empty string
"hello"11Single line, no newline
"a\nb"22Normal two-line string
"a\nb\n"32Trailing newline trap
"a\r\nb"22Windows CRLF line ending
"\n\n\n"43Three empty logical lines if trailing newline is excluded

Common bug

You may think this is one line, but split newline JavaScript behavior creates an extra empty string after the final separator.

For strings under 10MB, split('\n') and a regex counter usually run in a few milliseconds on modern hardware. For strings over 100MB, a regex or loop that counts newline characters can use less memory because it does not create an array containing every line. That matters when performance is part of the requirement.

Method 2: Count Lines in a File (Browser / FileReader API)

Use this method when

A user selects a text file in the browser and you want to count lines without uploading the file to a server.

Avoid this method when

The file is large enough that reading it all with FileReader could freeze the tab. Use file.stream() instead.

For browser file inputs under about 50MB, FileReader is the simplest option:

function countLinesAccurate(str) {
  if (str.length === 0) return 0;

  const newlineMatches = str.match(/\r\n|\r|\n/g) || [];
  const endsWithNewline = /\r\n|\r|\n$/.test(str);

  return newlineMatches.length + (endsWithNewline ? 0 : 1);
}

function countLinesInFile(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();

    reader.onload = (event) => {
      const text = event.target.result;
      resolve(countLinesAccurate(String(text)));
    };

    reader.onerror = () => reject(reader.error);
    reader.readAsText(file);
  });
}

document.getElementById('fileInput').addEventListener('change', async (event) => {
  try {
    const file = event.target.files[0];
    if (!file) return;

    const count = await countLinesInFile(file);
    console.log(`Line count: ${count}`);
  } catch (error) {
    console.error('Could not count file lines:', error);
  }
});

For larger browser files, use File.stream() with TextDecoder. This avoids storing the entire file as one giant string.

async function countLinesStream(file) {
  const reader = file.stream().getReader();
  const decoder = new TextDecoder();

  let lineCount = 0;
  let remainder = '';
  let lastChunkEndedWithNewline = false;

  while (true) {
    const { done, value } = await reader.read();

    if (done) {
      if (remainder.length > 0) {
        lineCount++;
      } else if (lineCount > 0 && lastChunkEndedWithNewline) {
        // Do not add an extra line for a trailing newline.
      }
      break;
    }

    const chunk = remainder + decoder.decode(value, { stream: true });
    const normalized = chunk.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
    const lines = normalized.split('\n');

    remainder = lines.pop() ?? '';
    lineCount += lines.length;
    lastChunkEndedWithNewline = normalized.endsWith('\n');
  }

  return lineCount;
}

Use FileReader when the file is small and you want simple code. Use file.stream() when the file is large, when you need a progress indicator, or when browser memory pressure matters. Both approaches keep the text local to the user's device.

Method 3: Count Lines in Node.js (Small Files)

Use this method when

You need to count lines in file Node.js scripts and the file is comfortably below 50MB.

Avoid this method when

The file may grow into hundreds of MB. A full read can consume much more memory than the file size.

For small files, fs.promises.readFile is readable and reliable.

const fs = require('node:fs/promises');

function countLinesAccurate(str) {
  if (str.length === 0) return 0;

  const newlineMatches = str.match(/\r\n|\r|\n/g) || [];
  const endsWithNewline = /\r\n|\r|\n$/.test(str);

  return newlineMatches.length + (endsWithNewline ? 0 : 1);
}

async function countLinesAsync(filePath) {
  const content = await fs.readFile(filePath, 'utf8');
  return countLinesAccurate(content);
}

async function main() {
  try {
    const count = await countLinesAsync('./data.txt');
    console.log(`Lines: ${count}`);
  } catch (error) {
    console.error('Could not count lines:', error);
    process.exitCode = 1;
  }
}

main();

Synchronous code is acceptable for quick one-off scripts:

const fs = require('node:fs');

function countLinesSync(filePath) {
  const content = fs.readFileSync(filePath, 'utf8');
  return countLinesAccurate(content);
}

console.log(countLinesSync('./data.txt'));

The rule is simple: fs.readFile is convenient, but it reads the entire file into memory. Use it for small known files. Do not use it as a general answer to count lines in file Node.js when log files, exports, or generated data may be large. If you want to count lines in JavaScript automation that runs on unknown inputs, streams are the safer default.

Method 4: Count Lines in Large Files with Node.js Streams

Use this method when

You need a scalable Node.js line counter for files over 50MB.

Choose between

readline for clarity, or Buffer chunk scanning for maximum throughput.

This is the most important section if you care about count lines JavaScript performance. A full read can look faster on tiny files, but it becomes fragile as file sizes grow.

Method 4A: Node.js readline count lines

readline is the official, readable streaming API for line-by-line processing. The crlfDelay: Infinity option matters because it treats Windows \r\n as one line break.

const fs = require('node:fs');
const readline = require('node:readline');

function countLinesReadline(filePath) {
  return new Promise((resolve, reject) => {
    let lineCount = 0;

    const rl = readline.createInterface({
      input: fs.createReadStream(filePath),
      crlfDelay: Infinity,
    });

    rl.on('line', () => {
      lineCount++;
    });

    rl.on('close', () => {
      resolve(lineCount);
    });

    rl.on('error', reject);
  });
}

async function main() {
  try {
    const count = await countLinesReadline('./large.log');
    console.log(`Lines: ${count}`);
  } catch (error) {
    console.error(error);
    process.exitCode = 1;
  }
}

main();

Use Node.js readline count lines code when you may later need to inspect each line, filter it, or parse fields while counting.

Method 4B: Buffer chunk scan

If you only need the count, scanning raw bytes is faster because it avoids creating a JavaScript string for every line.

const fs = require('node:fs');

function countLinesBuffer(filePath) {
  return new Promise((resolve, reject) => {
    let newlineCount = 0;
    let sawAnyByte = false;
    let lastByte = null;
    const NEWLINE = 10; // '\n'

    const stream = fs.createReadStream(filePath, {
      highWaterMark: 64 * 1024,
    });

    stream.on('data', (chunk) => {
      sawAnyByte = true;

      for (let i = 0; i < chunk.length; i++) {
        if (chunk[i] === NEWLINE) {
          newlineCount++;
        }
      }

      lastByte = chunk[chunk.length - 1];
    });

    stream.on('end', () => {
      if (!sawAnyByte) {
        resolve(0);
        return;
      }

      // wc -l counts newline bytes. Most text editors count the final
      // unterminated row as a line too, so add 1 when the file does not
      // end with '\n'.
      resolve(lastByte === NEWLINE ? newlineCount : newlineCount + 1);
    });

    stream.on('error', reject);
  });
}

This scanner handles the final-line boundary explicitly. If a file has a\nb with no newline after b, it returns 2. If the file is a\nb\n, it returns 2 rather than 3.

Did you know?

Node.js memory limits depend on version and platform, but a process still has a finite V8 heap. A 500MB file can require much more than 500MB after UTF-16 string decoding and split() creates thousands or millions of array entries. That is why large files should use streams.

Benchmark: JavaScript line counting performance

These benchmark values are directional measurements for line-oriented ASCII text on Node.js 20 LTS, Apple M2 Pro, macOS, SSD storage, averaged across repeated local runs. Hardware, encoding, line length, storage, and antivirus indexing can change absolute timings. The stable lesson is the relative shape: full reads are simple, readline is scalable, and Buffer scanning is fastest for raw counts.

Method10MB file100MB file1GB filePeak memoryBest fit
readFileSync + splitabout 15msabout 180msoften OOM or unstablefile size plus split arraySmall trusted files
readline streamabout 45msabout 380msabout 3.8sunder 10MBReadable large-file code
Buffer chunk scanabout 8msabout 65msabout 620msunder 5MBFast raw line counts

If you are on Linux or macOS and do not need JavaScript, wc -l is often faster still. The cross-platform command-line overview in the file line counting guide compares terminal options and GUI workflows.

Method 5: Count Lines in React, Vue, and Vanilla JS Textareas

Use this method when

You are building a live counter for textarea input in React, Vue, or plain JavaScript.

Remember

Textarea visual rows are not the same thing as text lines. Word wrapping creates soft visual lines without adding \n.

React controlled input:

function countLinesAccurate(str) {
  if (str.length === 0) return 0;

  const newlineMatches = str.match(/\r\n|\r|\n/g) || [];
  const endsWithNewline = /\r\n|\r|\n$/.test(str);

  return newlineMatches.length + (endsWithNewline ? 0 : 1);
}

function LineCounterTextarea() {
  const [text, setText] = React.useState('');
  const lineCount = React.useMemo(() => countLinesAccurate(text), [text]);

  return (
    <div>
      <textarea
        value={text}
        onChange={(event) => setText(event.target.value)}
        rows={10}
      />
      <p>Lines: {lineCount}</p>
    </div>
  );
}

Vue 3 computed value:

import { computed, ref } from 'vue';

const text = ref('');

const lineCount = computed(() => {
  return countLinesAccurate(text.value);
});

Vanilla JavaScript:

const textarea = document.querySelector('textarea');
const counter = document.querySelector('#line-count');

textarea.addEventListener('input', () => {
  const lines = countLinesAccurate(textarea.value);
  counter.textContent = `Lines: ${lines}`;
});

Do not use textarea.rows for line counting. That attribute controls the visible height of the element. It does not tell you how many newline characters are in the value, and it does not account for soft wrapping caused by CSS width.

Method 6: Count Non-Empty Lines and Code Lines

Use this method when

Blank lines, comments, and source-code metrics matter more than raw physical line count.

Avoid this method when

You need production-grade multi-language code metrics. Use cloc, tokei, or a language-aware parser.

Counting non-empty lines is straightforward:

function countNonEmptyLines(str) {
  return str
    .split(/\r\n|\r|\n/)
    .filter((line) => line.trim().length > 0)
    .length;
}

console.log(countNonEmptyLines("a\n\nb\n")); // 2

Counting lines of code is more subjective. This simple version excludes blank lines and obvious single-line comments:

function countCodeLines(str, language = 'js') {
  const lines = str.split(/\r\n|\r|\n/);
  const commentPatterns = {
    js: /^\s*(\/\/|\/\*|\*)/,
    ts: /^\s*(\/\/|\/\*|\*)/,
    python: /^\s*#/,
    css: /^\s*(\/\*|\*)/,
  };

  const pattern = commentPatterns[language] || commentPatterns.js;

  return lines.filter((line) => {
    const trimmed = line.trim();
    return trimmed.length > 0 && !pattern.test(trimmed);
  }).length;
}

To count a directory in Node.js:

const fs = require('node:fs/promises');
const path = require('node:path');

async function countLinesInDirectory(dirPath, extensions = ['.js', '.ts']) {
  const results = { total: 0, byFile: {} };

  async function walk(currentDir) {
    const entries = await fs.readdir(currentDir, { withFileTypes: true });

    for (const entry of entries) {
      const fullPath = path.join(currentDir, entry.name);

      if (entry.isDirectory()) {
        if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
          await walk(fullPath);
        }
        continue;
      }

      if (extensions.includes(path.extname(entry.name))) {
        const content = await fs.readFile(fullPath, 'utf8');
        const count = countNonEmptyLines(content);
        results.byFile[fullPath] = count;
        results.total += count;
      }
    }
  }

  await walk(dirPath);
  return results;
}

This is useful for small internal scripts. For a real codebase report, a dedicated source-code counter understands block comments, generated files, vendored folders, embedded languages, and language-specific syntax.

Method Comparison Table

This table is the quick reference version of the article.

MethodScenarioSize limitPerformanceComplexity
split('\n').length with correctionString already in memoryMemory limitVery fastLow
FileReader APIBrowser file uploadUnder 50MB recommendedFast enoughLow
file.stream()Browser large fileNo practical fixed limitFastMedium
fs.readFile + splitNode.js small fileUnder 50MB recommendedVery fastLow
readline streamNode.js large fileNo practical fixed limitGoodMedium
Buffer chunk scanNode.js huge fileNo practical fixed limitFastestMedium

If your goal is "just count this now," use the online line counter instead of writing utility code. If your goal is automation, pick the smallest implementation that still handles your largest expected input. That is the core rule when you count lines in JavaScript across both browser and Node.js projects.

Edge Cases You Should Test

Before shipping a line counter, test these cases explicitly. These are the cases that make "count lines in JavaScript" look trivial in a snippet but fragile in a real app.

CaseInputExpected logical countWhy it matters
Empty string""0split('\n').length returns 1
Single unterminated line"hello"1No newline character exists
Two lines"a\nb"2Basic Unix newline
Trailing newline"a\nb\n"2Prevents off-by-one bugs
Windows newline"a\r\nb"2CRLF should be one line break
Old Mac newline"a\rb"2Rare but still appears in legacy text
Only newline characters"\n\n\n"3Decide how your app defines empty logical lines
Non-empty only"a\n\nb"2Requires a filter, not a raw count

The most important policy decision is whether a trailing newline creates a new logical line. Most text editors and user-facing tools say no. Some low-level tools, including wc -l, count newline bytes instead. State your policy in code comments when the behavior matters.

Complete Utility: Reusable JavaScript Line Counter

This small utility centralizes the string behavior so your browser, React, and Node.js small-file code all agree. It is a practical base when you need to count lines in JavaScript in more than one part of a codebase.

export function countLines(text, options = {}) {
  const {
    countTrailingNewline = false,
    ignoreEmpty = false,
  } = options;

  if (text.length === 0) return 0;

  const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
  let lines = normalized.split('\n');

  if (!countTrailingNewline && lines[lines.length - 1] === '') {
    lines = lines.slice(0, -1);
  }

  if (ignoreEmpty) {
    lines = lines.filter((line) => line.trim().length > 0);
  }

  return lines.length;
}

console.log(countLines("a\nb\n")); // 2
console.log(countLines("a\nb\n", { countTrailingNewline: true })); // 3
console.log(countLines("a\n\nb", { ignoreEmpty: true })); // 2

This is the best default answer for JavaScript count lines in string when you control the codebase and want explicit behavior.

FAQ

Why does split('\n').length return one extra line?

Because split() returns the text before and after each separator. A trailing separator has nothing after it, so JavaScript returns an empty string as the final array item. "hello\n".split('\n') becomes ["hello", ""], which has length 2.

How do I count lines in a .js file in Node.js?

For a small .js file, use fs.promises.readFile(filePath, 'utf8') and the accurate string counter above. For large files, use readline.createInterface({ input: fs.createReadStream(filePath), crlfDelay: Infinity }).

What is the fastest way to count lines in a large file?

The Buffer chunk scan is usually the fastest JavaScript method because it counts raw newline bytes. Use it when you only need a number. Use readline when you also need to inspect or transform each line.

How do I count lines without counting empty lines?

Split on cross-platform newline sequences and filter:

const count = text
  .split(/\r\n|\r|\n/)
  .filter((line) => line.trim().length > 0)
  .length;

Does \r\n count as one line or two?

It should count as one line ending. \r\n is the Windows newline sequence. In Node.js streams, crlfDelay: Infinity tells readline to treat the sequence as a single line break.

How do I count lines in a string in the browser?

Use the same string counter shown in Method 1. JavaScript string methods behave the same in modern browsers and Node.js for this task.

How do I count lines in React?

Keep the textarea value in state, then derive the count from that value. Count \n characters in the value, not visual rows created by wrapping.

Don't Want to Write Code for a Quick Count?

If you just need to count lines in a file or string right now without writing a single line of code, paste it into the Line Counter tool. It handles text, code files, and uploads in the browser, with line, word, character, paragraph, and reading-time stats.

The code above is the right choice when you are building the behavior into an app or script. For one-off counting, the tool is faster than opening an editor, creating a file, and debugging newline edge cases.

Quick CTA

Don't want to write code for a quick count? Paste text or upload a file in the browser-based Line Counter tool and get the answer instantly.

Frequently Asked Questions

Why does split('\n').length return one extra line?

When a string ends with a newline, split('\n') creates an empty string after the final separator. For example, 'hello\n'.split('\n') returns ['hello', ''], so the length is 2 even though most people think of it as one text line.

How do I count lines in a .js file in Node.js?

For a small file, read it with fs.promises.readFile(filePath, 'utf8') and pass the content to an accurate string line counter. For large files, use readline.createInterface with crlfDelay: Infinity or scan newline bytes in a read stream.

What is the fastest way to count lines in a large file?

A Buffer chunk scan is usually the fastest JavaScript approach because it counts newline bytes directly without allocating one string per line. It is less expressive than readline, but it scales better for hundreds of MB or GB-sized files.

How do I count lines without counting empty lines?

Split the string into lines and filter with line.trim().length > 0. For huge files, do the same logic in a stream so you do not load the whole file into memory.

Does \r\n count as one line or two?

Windows CRLF is one line ending made from two characters. In Node.js readline, set crlfDelay: Infinity so \r\n is treated as a single line break.

How do I count lines in a string in the browser?

Use the same string functions you would use in Node.js. For a value that is already in memory, a corrected split-based counter is enough for most inputs under 10MB.

How do I count lines in React?

Store the textarea value in component state, derive the line count from that state, and update it on every input event. Count hard newline characters, not textarea visual rows.

Related Guides