All Articles

How does String.padStart actually work?

Previously, I shared my usage of padStart to elegantly replace what would’ve been loads of if statements. This magical method threw me off my rocker. I simply couldn’t believe it existed.

What it does

Mozilla Developer Network (MDN) Docs:

The padStart() method pads the current string with another string (repeated, if needed) so that the resulting string reaches the given length. The padding is applied from the start (left) of the current string.

Keep prepending a string to another string until the target length is met.

If the length is already less than the original string’s length, nothing happens.

And since padStart returns a string, we can chain its methods.

See? 1, 2, 3, 4, and 5 are all less than or equal to world’s length of 5, so padStart doesn’t do anything.

Browser support

Unfortunately, support’s currently “meh”

Desktop supportMobile support

You can either use babel-polyfill or the polyfill by MDN.

Here’s MDN’s polyfill.

Some points of interest:

  • Prototypes (lines 1 and 2)
  • Bitwise operators (line 4)
  • padString.repeat (line 14)
  • padString.slice (line 17)

I’m down to step through them if you are 😁

Lines 1 and 2 aren’t that bad: “If padStart isn’t supported by the browser, let’s create our own padStart and add it” (that’s polyfill-ing in a nutshell).

A common way to check a method’s browser support is to inspect its object’s prototype. Since padStart is a string method, it should exist on String.prototype.

My old version of Safari doesn’t support padStart.

My Safari’s padStart support

But my Chrome and Firefox do.

Chrome padStart supportFirefox padStart support

Consider this safety check on line 1

if (!String.prototype.padStart) {
}

That if statement would only return true in my old Safari. It returns false in Chrome/Firefox, so no polyfill-ing happens.

Moving on, line 2 creates a new function called padStart and assigns it to String.prototype.padStart. Because of JavaScript’s inheritance model, any string created afterwards can use padStart.

This function takes two parameters

1. targetLength: How long should the resulting string be?

2. padString: What are we padding it with?

Let’s shower this code with debugger statements.

I also removed that if statement from line 1, so even the native String.prototype.padStart will be overridden by this function–makes it useful if you want to debug in Chrome.

Don’t override prototypes in production, kids!

Using our initial example

'world'.padStart(11, 'hello ');

Check out line 2. We see that targetLength and padString made their way into our function. No craziness yet, but it’s coming. I’ve been avoiding line 5 long enough.

Bitwise operators

The comment above line 5 briefly describes its purpose: “If targetLength is a number, round it down. If it’s not a number, make it 0”.

Bitwise operators make this possible.

targetLength >> 0;

This operator >> is known as a sign-propagating right shift (LOLWUT?). You use it with two numbers

a >> b

What this does:

  1. a is converted into binary (details here).
  2. Binary a gets right-shifted b times.

Our targetLength is 11–that’s 1011 in binary (here’s a converter if you don’t believe me 😉).

A side effect of converting to binary is that numbers get rounded down and most non-numbers become 0.

Try the following examples

See? Fractions become whole numbers. Non-numbers become 0, with one notable exception…

Binary is just 1’s and 0’s, right? Those 1’s and 0’s represent “on” and “off” switches–true and false. true’s binary form is 1, and false’s binary form is 0. Just keep that in mind.

So now that we’ve “sanitized” targetLength, we begin the right-shifting.

Right-shift means you move each bit to the right n times. That’s it.

Here’s a PowerPoint visualization of 11 >> 1 (I forgot how great PowerPoint actually is).

Turn 11 into 1011 and right-shift it 1 time. Your end result is 101, which is 5 in binary.

But our code says targetLength >> 0.

So we’re right-shifting 0 times…

The whole point of right-shifting 0 times is to abuse that side effect of converting targetLength into binary. We don’t actually want to shift anything because that’ll change the value.

Moving on

Jump to line 7’s debugger now. targetLength has been sanitized. Next!

Line 11.

padString = String(padString || ' ');

If we don’t provide a padString argument, it defaults to an empty space. I actually never noticed until now.

Line 17.

Notice how line 13 had another safety check, “If the original string’s length is greater than targetLength, don’t do anything. Just return the original string”

That makes sense because if our targetLength is 1, but the string is already 10 characters, what’s the point? We demonstrated that earlier with

// just returns 'world'
'world'.padStart(0, 'hello ');

Line 18 determines how many more characters we need by subtracting targetLength from the original string’s length. We need 6, in this case.

Line 27.

We skipped that if statement on line 20 because targetLength and padString.length just happened to be the same, but we’ll revisit that soon.

For now, we’re stopped right before line 29. Let’s break it up.

padString.slice(0, targetLength);

The good old String.prototype.slice method.

MDN Docs:

The slice() method extracts a section of a string and returns it as a new string.

It’s index-based, so we’re starting at index 0 of padString, and grabbing the amount of characters equal to targetLength. It’s kind of like

Return that sliced padString combined with the original string, and you’re done!

Almost done

I’d normally conclude here, but we haven’t explored that if statement on line 20. To make sure we hit it this time, let’s try another earlier example

'yo'.padStart(20, 'yo');

I skipped to line 20 because we already know what happens up to this point.

if (targetLength > padString.length)

targetLength is 18, and padString is 'yo', with 2 as its length. 18 > 2, so what next?

padString += padString.repeat(targetLength / padString.length);

Remember, padStart returns a sliced padString + original string. If you want to pad 'yo' with 'yo' until it’s 20 characters long, you’ll have to repeat many times. This is where that logic happens, using padString.repeat.

MDN Docs:

The repeat() method constructs and returns a new string which contains the specified number of copies of the string on which it was called, concatenated together.

So it copy/pastes the string n times.

In order to find out how many repeats we need, divide targetLength by padString.length.

Repeat 'yo' 9 times and get a string of 'yo's that is 18 characters long. Add that to your original 'yo', and your final count is 20 characters.

Mission accomplished. Until next time!