Why I Switched to Using str.startswith

One of my favorite files to use in my programming classes is the /etc/passwd file on Unix machines. This file stores user information on most Unix systems; although passwords were moved to another file or database long ago, the name stuck.

The format of this file is perfect for teaching students to read from files, use "split" and "join" operations, summarize file contents, and learn text manipulation.

For example, you can iterate over the rows of /etc/passwd in JavaScript with code like this:

const fs = require("fs");

fs.readFileSync("/etc/passwd", "utf-8")
  .split("\n")
  .forEach((line) => console.log(line));

To print only usernames, you could write:

fs.readFileSync("/etc/passwd", "utf-8")
  .split("\n")
  .forEach((line) => console.log(line.split(":")[0]));

However, this approach doesn’t work quite right on Macs. That’s because Apple doesn’t rely heavily on /etc/passwd. Instead, we get a set of Unix-style comments (lines beginning with "#") warning that /etc/passwd isn’t the primary source of user information:

##
# User Database
#
# Note that this file is consulted directly only when the system is running
# in single-user mode.  At other times this information is provided by
# Open Directory.
#
# See the opendirectoryd(8) man page for additional information about
# Open Directory.
##
nobody:*:-2:-2::0:0:Unprivileged User:/var/empty:/usr/bin/false
root:*:0:0::0:0:System Administrator:/var/root:/bin/sh

These comments are fine for a human reader but can cause issues when a program is reading the file. Our simple loop needs to filter out lines that start with #.

Even with basic JavaScript experience, a developer will likely know to filter out these comment lines like this:

fs.readFileSync("/etc/passwd", "utf-8")
  .split("\n")
  .forEach((line) => {
    if (line[0] !== "#") {
      console.log(line.split(":")[0]);
    }
  });

This approach worked for me for years. However, I’ve recently started using JavaScript’s str.startsWith method, which is both more readable and clearer for less technically experienced readers. The startsWith method returns a boolean indicating whether the string begins with the specified argument:

let s = "abcd";
console.log(s.startsWith("a")); // true
console.log(s.startsWith("z")); // false

We can then rewrite the code as:

fs.readFileSync("/etc/passwd", "utf-8")
  .split("\n")
  .forEach((line) => {
    if (!line.startsWith("#")) {
      console.log(line.split(":")[0]);
    }
  });

Switching to startsWith has an advantage over using != or ==: the code is more readable, especially for a less technical audience. You might argue that anyone who doesn’t understand s[0] shouldn’t be tampering with your code, but I prefer code that’s easy to understand and flows naturally, even if it’s a bit slower or more verbose.

An additional advantage of str.startsWith over ==, !=, or s[0] is that it accepts a string, not just a single character. This allows us to check the beginning of a string for multiple characters, as in:

let s = "abcd";
console.log(s.startsWith("ab")); // true
console.log(s.startsWith("az")); // false

Using only == and != would require additional logic (e.g., && or ||) to check multiple characters, while str.startsWith keeps this simple and compact.

Similarly, str.endsWith works by checking if a string ends with the specified characters.

I hope this helps make your JavaScript code more readable!

Get my free, weekly JavaScript tutorials

Want to improve your JavaScript fluency?

Every week, I send a new full-length JavaScript article to thousands of developers. Learn about asynchronous programming, closures, and best practices — as well as general tips for software engineers.

Join today, and level up your JavaScript every Sunday!

Thank you, Taha, for your amazing newsletter. I’m really benefiting from the valuable insights and tips you share.

- Remi Egwuda