1

The Module That Broke the Internet

Why this matters

In March 2016, a developer un-published an eleven-line npm package called left-pad. Within hours, Babel, React, and thousands of other projects stopped building — npm install couldn’t resolve the transitive dependency. The incident kicked off years of debate about supply-chain risk, micro-dependencies, and what belongs in a standard library. This lecture replays the story by reading the actual code, running its tests, then replacing the whole module with one line of modern JavaScript.

🎯 You will learn to

  • Read a tiny but production-shipped JavaScript module and identify its contract
  • Apply Node’s built-in assert to encode a contract as runnable tests
  • Evaluate what behaviors a test suite actually protects vs. assumes

The original leftPad

The pre-incident left-pad package was famously short — variations weighed in between 8 and 17 lines. The version below is the canonical one from before the optimisation patches. leftPad(str, len, ch) left-pads str with ch until it reaches length len:

function leftPad (str, len, ch) {
  str = String(str);
  var i = -1;
  if (!ch && ch !== 0) ch = ' ';
  len = len - str.length;
  while (++i < len) {
    str = ch + str;
  }
  return str;
}
Line What it does
str = String(str) Coerce non-string input (numbers, booleans) into a string — this matters in step 2
if (!ch && ch !== 0) ch = ' ' Default pad character is a space; but 0 is allowed even though it’s falsy
len = len - str.length Reuse len as the number of pad characters still to prepend
while (++i < len) Prepend ch once per missing character

The contract — as tests

The test file on the right pane encodes the main contract using Node’s built-in node:assert. Each test(...) block calls leftPad and asserts on the result. The runner prints or per test plus a N passed, M failed line at the end.

✏️ Predict before you run

Look at the six tests in leftpad.test.js. Which of these is most likely to behave differently if we later replace leftPad with a built-in language feature?

  • (a) pads with spaces by default — the default-' ' case
  • (b) pads with a custom character — the canonical happy path
  • (c) returns the string unchanged when length is already met — the no-op case
  • (d) accepts a number as input (coerces to string) — relies on String(str)

Commit to a letter, then click ▶ Run.

Reveal (after committing) **(d)** is the trap. The other five tests describe behavior any string-padding function would have — including a built-in. (d) leans on a JavaScript-specific coercion the original module performs explicitly (`str = String(str)`). A naive built-in replacement won't do this. We'll watch it break in step 2.

Task

  1. Read leftpad.js (left pane) and leftpad.test.js (right pane).
  2. Click ▶ Run to execute node leftpad.test.js.
  3. Confirm all six tests pass.
Starter files
leftpad.js
// The original left-pad — the eleven-line module that broke the
// internet in March 2016. Reproduced here verbatim from the
// pre-incident package.
//
// Pads `str` on the LEFT with character `ch` until its total
// length is `len`. If `str` is already that long (or longer),
// it is returned unchanged.

function leftPad (str, len, ch) {
  str = String(str);
  var i = -1;
  if (!ch && ch !== 0) ch = ' ';
  len = len - str.length;
  while (++i < len) {
    str = ch + str;
  }
  return str;
}

module.exports = leftPad;
leftpad.test.js
// Contract tests for the leftPad module.
//
// Each `test(name, fn)` call runs `fn()` and reports ✓ if it
// completes without throwing, ✗ otherwise. The body uses Node's
// built-in `node:assert` module (no third-party test framework
// required).
//
// Run with:  node leftpad.test.js

const assert = require('node:assert');
const leftPad = require('./leftpad');

let passed = 0;
let failed = 0;

function test(name, fn) {
  try {
    fn();
    console.log('' + name);
    passed++;
  } catch (e) {
    console.log('' + name);
    console.log('    ' + e.message);
    failed++;
  }
}

console.log('Running leftPad contract tests:\n');

// --- The main contract -------------------------------------------

test('pads with spaces by default', () => {
  assert.strictEqual(leftPad('abc', 5), '  abc');
});

test('pads with a custom character', () => {
  assert.strictEqual(leftPad('abc', 5, '0'), '00abc');
});

test('returns the string unchanged when length is already met', () => {
  assert.strictEqual(leftPad('hello', 5, '0'), 'hello');
});

test('returns the string unchanged when target length is shorter', () => {
  assert.strictEqual(leftPad('hello', 3, '0'), 'hello');
});

test('handles single-character padding to a wider target', () => {
  assert.strictEqual(leftPad('x', 4, '*'), '***x');
});

// --- The JS-specific coercion the original module performs -------
//
// The original leftPad starts with `str = String(str)`, so it
// accepts numbers and coerces them to a string. A naive
// replacement that calls `.padStart()` directly on `str` will
// break this test, because numbers do not have a .padStart()
// method. See step 2.

test('accepts a number as input (coerces to string)', () => {
  assert.strictEqual(leftPad(7, 4, '0'), '0007');
});

console.log(`\n${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);
2

One Built-In Replaces Eleven Lines: String.prototype.padStart

Why this matters

The reason left-pad no longer needs to exist is ES2017 (a.k.a. ES8). That language revision added String.prototype.padStart(targetLength, padString) and its sibling padEnd to the standard library. Node.js 8+ ships them. Two million npm dependents could replace the whole module with one method call — if they’re willing to accept the spec-correct semantics that come with it.

🎯 You will learn to

  • Apply String.prototype.padStart to replace the body of leftPad
  • Analyze which behaviors are preserved and which break under the swap
  • Evaluate when “spec-correct” differs from “what the previous implementation actually did”

String.prototype.padStart

'abc'.padStart(5)        // '  abc'
'abc'.padStart(5, '0')   // '00abc'
'hello'.padStart(3)      // 'hello'  ← no truncation, matches leftPad

Three reasons this is “the most efficient Node.js call that replaces left-pad”:

  1. Zero dependencies — it’s part of the language runtime.
  2. Zero bytes shipped — no npm install, no require.
  3. Implemented in V8 C++ — orders of magnitude faster than a while loop allocating new strings each iteration.

The naive swap

The temptation is to drop the body of leftPad and forward directly to the built-in:

function leftPad (str, len, ch) {
  return str.padStart(len, ch);
}

Two lines. The whole module.

✏️ Predict before you run

How many of the six tests still pass?

  • (a) All sixpadStart is the standard library version of exactly this function.
  • (b) Five — one test relies on a JS-specific behavior the built-in doesn’t replicate.
  • (c) FourpadStart has different default behavior for the pad character.
  • (d) ZeropadStart truncates instead of preserving short strings.

Commit to a letter, then click ▶ Run and read the output carefully.

Reveal (after running) **(b)**. Five of the original six pass. The one that breaks is `accepts a number as input` — `leftPad(7, 4, '0')` throws `TypeError: str.padStart is not a function`, because `(7).padStart` is `undefined`. Numbers don't have the method; only strings do. The original module masked this with its first line, `str = String(str)`. The naive swap dropped that line — so the contract for non-string input is gone.

Task

  1. Replace the body of leftPad in leftpad.js with return str.padStart(len, ch);
  2. Click ▶ Run. Five tests should pass, one should fail.
  3. Read the failing test’s name and error message. Confirm it’s the number-coercion case.
  4. Step 3 will fix it — but first, understand the break.
Starter files
leftpad.js
// The original left-pad — soon to be replaced with one line.
//
// 🛠️ TASK: Replace the body of leftPad with:
//
//     return str.padStart(len, ch);
//
// Then click ▶ Run. Five tests pass, one fails.

function leftPad (str, len, ch) {
  str = String(str);
  var i = -1;
  if (!ch && ch !== 0) ch = ' ';
  len = len - str.length;
  while (++i < len) {
    str = ch + str;
  }
  return str;
}

module.exports = leftPad;
leftpad.test.js
// Same contract tests as step 1 — unchanged. The point is to
// watch the test suite respond to a change in `leftpad.js`.

const assert = require('node:assert');
const leftPad = require('./leftpad');

let passed = 0;
let failed = 0;

function test(name, fn) {
  try {
    fn();
    console.log('' + name);
    passed++;
  } catch (e) {
    console.log('' + name);
    console.log('    ' + e.message);
    failed++;
  }
}

console.log('Running leftPad contract tests:\n');

test('pads with spaces by default', () => {
  assert.strictEqual(leftPad('abc', 5), '  abc');
});

test('pads with a custom character', () => {
  assert.strictEqual(leftPad('abc', 5, '0'), '00abc');
});

test('returns the string unchanged when length is already met', () => {
  assert.strictEqual(leftPad('hello', 5, '0'), 'hello');
});

test('returns the string unchanged when target length is shorter', () => {
  assert.strictEqual(leftPad('hello', 3, '0'), 'hello');
});

test('handles single-character padding to a wider target', () => {
  assert.strictEqual(leftPad('x', 4, '*'), '***x');
});

test('accepts a number as input (coerces to string)', () => {
  assert.strictEqual(leftPad(7, 4, '0'), '0007');
});

console.log(`\n${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);
3

Restore the Contract — String(str).padStart(len, ch)

Why this matters

The failing test in step 2 wasn’t a flaw in padStart — it was a flaw in our refactor. The original module had two responsibilities: coerce the input to a string, and pad it. We replaced the second with a built-in but dropped the first. Re-adding the coercion takes one function call, brings every test back to green, and produces the punch line of the lecture: the eleven-line module that broke the internet is now a one-liner.

🎯 You will learn to

  • Apply String(value) as an explicit coercion idiom
  • Synthesize a contract-preserving refactor that keeps the entire test suite green
  • Evaluate the trade-off between micro-dependencies and standard-library evolution

The fix

function leftPad (str, len, ch) {
  return String(str).padStart(len, ch);
}

String(value) — called without new — is the standard explicit-coercion idiom. It is identical in result to the original module’s str = String(str) line. With the coercion in place, leftPad(7, 4, '0') runs as String(7).padStart(4, '0')'7'.padStart(4, '0')'0007'. All six tests pass.

The before/after, side by side

// BEFORE — the famous eleven lines.
function leftPad (str, len, ch) {
  str = String(str);
  var i = -1;
  if (!ch && ch !== 0) ch = ' ';
  len = len - str.length;
  while (++i < len) {
    str = ch + str;
  }
  return str;
}

// AFTER — one line of ES2017.
function leftPad (str, len, ch) {
  return String(str).padStart(len, ch);
}

✏️ Predict before you run

Does the one-line version also default the pad character to ' ' when ch is undefined?

  • (a) No — padStart(len, undefined) pads with 'undefined' repeated.
  • (b) No — padStart(len, undefined) throws TypeError.
  • (c) Yes — the spec for padStart says “if padString is undefined, use a single space.”
  • (d) Yes — but only because we still call String(str), which secretly handles it.

Commit to a letter, then run.

Reveal (after running) **(c)**. The ECMAScript spec for `String.prototype.padStart` explicitly defaults `padString` to `' '` when it's `undefined`. That's why the first test (`pads with spaces by default`) passes without us writing `if (!ch && ch !== 0) ch = ' '`. The built-in is doing the work the original module did manually. (a) is what you'd get if you *explicitly* passed `'undefined'`. (b) only happens with `null`-coerced edge cases far outside the contract.

The real-world lesson

  • The standard library evolves. Three lines you write today may become one built-in five years from now. Watch the spec.
  • Micro-dependencies have a cost. Every npm install is a supply-chain decision. If you can replace a dependency with a one-line built-in, you reduce maintenance, attack surface, and bundle size.
  • Read the spec, not just the docs. padStart’s default-' ' rule is in TC39; without it, the one-liner wouldn’t preserve the contract.

Task

  1. In leftpad.js, replace the body of leftPad with return String(str).padStart(len, ch);
  2. Click ▶ Run.
  3. Confirm all six tests pass.
  4. Look at leftpad.js once more — that’s the entire module. Sit with that.
Starter files
leftpad.js
// 🛠️ TASK: Replace the body of leftPad with:
//
//     return String(str).padStart(len, ch);
//
// Then click ▶ Run. All six tests should pass.

function leftPad (str, len, ch) {
  return str.padStart(len, ch);  // still the naive swap — fix me!
}

module.exports = leftPad;
leftpad.test.js
// Same contract tests as steps 1 and 2 — unchanged.
// The point is to watch the same suite confirm correctness
// across three different implementations.

const assert = require('node:assert');
const leftPad = require('./leftpad');

let passed = 0;
let failed = 0;

function test(name, fn) {
  try {
    fn();
    console.log('' + name);
    passed++;
  } catch (e) {
    console.log('' + name);
    console.log('    ' + e.message);
    failed++;
  }
}

console.log('Running leftPad contract tests:\n');

test('pads with spaces by default', () => {
  assert.strictEqual(leftPad('abc', 5), '  abc');
});

test('pads with a custom character', () => {
  assert.strictEqual(leftPad('abc', 5, '0'), '00abc');
});

test('returns the string unchanged when length is already met', () => {
  assert.strictEqual(leftPad('hello', 5, '0'), 'hello');
});

test('returns the string unchanged when target length is shorter', () => {
  assert.strictEqual(leftPad('hello', 3, '0'), 'hello');
});

test('handles single-character padding to a wider target', () => {
  assert.strictEqual(leftPad('x', 4, '*'), '***x');
});

test('accepts a number as input (coerces to string)', () => {
  assert.strictEqual(leftPad(7, 4, '0'), '0007');
});

console.log(`\n${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);