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, refactoring it to modern JavaScript, and finally replacing the whole module with one line of String.prototype.padStart.

🎯 You will learn to

  • Read a tiny but production-shipped JavaScript module and identify its contract
  • Apply a Jest-style test suite (describe / test / expect) as the contract
  • Refactor a 2016-era while-loop implementation to a 2017-era String.prototype.repeat one
  • Evaluate what behaviors a test suite actually protects vs. assumes

The original leftpad

The pre-incident left-pad package was famously short — eleven lines. The version below is the canonical one from before the optimisation patches: var i = -1, a manual while (++i < length) loop, and a hand-rolled default for the pad character. It works, but every line is doing something modern JavaScript has a built-in for.

function leftpad (str, length, paddingCharacter) {
  str = String(str);
  var i = -1;
  if (!paddingCharacter && paddingCharacter !== 0) paddingCharacter = ' ';
  length = length - str.length;
  while (++i < length) {
    str = paddingCharacter + str;
  }
  return str;
}
Line What it does
str = String(str) Coerce non-string input (numbers, null, undefined) into a string — this matters in step 2
if (!paddingCharacter && paddingCharacter !== 0) ch = ' ' Hand-rolled default: space, but 0 is allowed too
length = length - str.length Reuse length as the number of pad characters still to prepend
while (++i < length) str = paddingCharacter + str Prepend one character per iteration — allocates a new string each pass

The driver: main.js

main.js is the small program above that exercises leftpad. By default it pads four numbers (124, 1, 90093, 9193) to width 5. If you pass command-line arguments via the argv field above the Run button, those numbers replace the defaults — try 7 42 1024 and watch the output line up.

The contract — as Jest-style tests

__tests__/leftpad_test.js encodes the contract in nine Jest-style expect(...).toBe(...) assertions. A tiny in-file runner at the top of the file gives us describe / test / expect without an npm install jest, so node __tests__/leftpad_test.js works as-is — the assertions are exactly what real Jest would evaluate.

Two buttons run two different things:

  • ▶ Runnode main.js <argv>. The number you type into the argv field is appended verbatim. The output is one padded line per number.
  • ✓ Testnode __tests__/leftpad_test.js. The output is a ✓ / ✗ line per test plus a summary count.

✏️ Predict before you run

The starter left-pad.js is the 2016 original. Three modern JavaScript features can replace three of its lines without changing the contract:

  1. paddingCharacter.repeat(n) returns paddingCharacter concatenated n times — one call replaces the while (++i < length) loop.
  2. Default parameters (paddingCharacter = ' ') replace the hand-rolled if (!paddingCharacter ...) default.
  3. An early-return guard (if (length < 1) return str) replaces the no-op behavior the while loop got “for free” when length <= 0.

Which of those three changes can you make without breaking any of the nine Jest tests?

  • (a) Only the loop replacement.
  • (b) Only the default-parameter change.
  • (c) All three — the tests describe the contract, not the implementation.
  • (d) None — refactoring will break the numeric / null / undefined tests.

Commit to a letter, then click ▶ Run and ✓ Test.

Reveal (after committing) **(c)**. The nine tests describe the *contract* — what `leftpad` returns for given inputs. They don't care how it's implemented. Any refactor that preserves the contract (correct default, no truncation, character repetition, `String()` coercion) will keep them green. (d) is the trap from step 2, not this one: the *built-in* `padStart` doesn't coerce, so dropping `String(str)` breaks 3 tests. But our refactor keeps `String(str)`.

Task

  1. Read left-pad.js (left pane) and __tests__/leftpad_test.js (right pane).
  2. Click ▶ Run to execute node main.js 124 1 90093 9193 — confirm it prints four right-aligned numbers.
  3. Click ✓ Test to execute the Jest suite. Confirm all nine tests pass against the 2016 original.
  4. Now refactor leftpad to the modern eight-line shape:
    • Replace the while (++i < length) loop with one call to paddingCharacter.repeat(length).
    • Replace the hand-rolled default with a default parameter paddingCharacter = ' '.
    • Add an explicit if (length < 1) return str guard so length <= 0 cases still return early.
  5. Click ✓ Test again. All nine tests should still pass.
  6. (Instructor mode) Click Solution to compare against the reference implementation.
Starter files
left-pad.js
// The original left-pad — the eleven-line module that broke the
// internet in March 2016. Reproduced here verbatim from the
// pre-incident package, lightly renamed (str/length/paddingCharacter
// instead of str/len/ch) to match the rest of the lecture.
//
// 🛠️ TASK: Refactor this to the modern eight-line shape (see the
// instructions panel). The nine Jest tests on the right MUST stay
// green throughout — they describe the contract, not the body.

module.exports = leftpad;
function leftpad (str, length, paddingCharacter) {
  str = String(str);
  var i = -1;
  if (!paddingCharacter && paddingCharacter !== 0) paddingCharacter = ' ';
  length = length - str.length;
  while (++i < length) {
    str = paddingCharacter + str;
  }
  return str;
}
main.js
// main.js — a small driver that pads numbers with spaces.
//
//   node main.js                    # default: 124 1 90093 9193
//   node main.js 7 42 1024          # use these numbers instead
//
// The argv field above the Run button feeds into process.argv.

const leftpad = require('./left-pad.js');

const argv = process.argv.slice(2);
const numbers = argv.length > 0 ? argv : [124, 1, 90093, 9193];

const len = 5;
for (const num of numbers) {
  console.log(leftpad(num, len, ' '));
}
__tests__/leftpad_test.js
// Jest-style contract tests for the leftpad module.
//
// Run with:  node __tests__/leftpad_test.js
//
// In a real project you'd `npm install jest && npx jest`. The
// 15-line shim at the top of this file gives us the same
// describe / test / expect API as a plain Node script, so the
// demo doesn't need to install Jest.

// === Minimal Jest-compatible runner ====================================
let __passed = 0, __failed = 0;
function describe(name, fn) { console.log(name); fn(); }
function test(name, fn) {
  try { fn(); console.log('' + name); __passed++; }
  catch (e) { console.log('' + name); console.log('    ' + e.message); __failed++; }
}
function expect(actual) {
  return { toBe(expected) {
    if (!Object.is(actual, expected)) {
      throw new Error('Expected ' + JSON.stringify(expected) + ', got ' + JSON.stringify(actual));
    }
  }};
}
// ========================================================================

const leftpad = require('../left-pad.js');

describe('leftpad', () => {

  test('should pad a short string with spaces to the specified length', () => {
    expect(leftpad('foo', 5)).toBe('  foo');
  });

  test('should return the original string if its length is equal to the target length', () => {
    expect(leftpad('foobar', 6)).toBe('foobar');
  });

  test('should return the original string if its length is greater than the target length', () => {
    expect(leftpad('foobar', 3)).toBe('foobar');
  });

  test('should pad with a custom character', () => {
    expect(leftpad('foo', 5, '0')).toBe('00foo');
  });

  test('should return the original string when target length is 0', () => {
    expect(leftpad('foo', 0)).toBe('foo');
  });

  test('should handle numeric input by converting it to a string', () => {
    expect(leftpad(123, 5, '0')).toBe('00123');
  });

  test('should handle null input by converting it to the string "null"', () => {
    expect(leftpad(null, 6)).toBe('  null');
  });

  test('should handle undefined input by converting it to the string "undefined"', () => {
    expect(leftpad(undefined, 12)).toBe('   undefined');
  });

  test('should pad an empty string', () => {
    expect(leftpad('', 4, 'x')).toBe('xxxx');
  });
});

console.log('\n' + __passed + ' passed, ' + __failed + ' failed');
process.exit(__failed > 0 ? 1 : 0);
package.json
{
  "name": "left-pad-lecture",
  "version": "1.0.0",
  "scripts": {
    "test": "jest",
    "dev": "node main.js"
  },
  "devDependencies": {
    "jest": "^30.2.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

'foo'.padStart(5)         // '  foo'
'foo'.padStart(5, '0')    // '00foo'
'foobar'.padStart(3)      // 'foobar'  ← 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 allocating new strings in JS.

The naive swap

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

function leftpad (str, length, paddingCharacter) {
  return str.padStart(length, paddingCharacter);
}

Two lines. The whole module.

✏️ Predict before you run

How many of the nine tests still pass?

  • (a) All ninepadStart is the standard library version of exactly this function.
  • (b) Six — three tests rely on a JS-specific coercion the built-in doesn’t replicate.
  • (c) FivepadStart has different default behavior for the pad character.
  • (d) ZeropadStart truncates instead of preserving short strings.

Commit to a letter, then click ▶ Run (try node main.js 124 1 90093 9193) and ✓ Test My Work. Read the test output carefully.

Reveal (after running) **(b)**. Six of the nine pass. The three that break are the non-string-input tests: `leftpad(123, 5, '0')`, `leftpad(null, 6)`, and `leftpad(undefined, 12)`. Each throws `TypeError: str.padStart is not a function` (or similar) because `(123).padStart`, `null.padStart`, and `undefined.padStart` are not defined — only the *string* prototype has `padStart`. The original module masked all three with its first line, `str = String(str)`. The naive swap dropped that line — so the contract for non-string input is gone. Note: when you click **▶ Run**, you'll also see `main.js` crash with the same `TypeError`, because the default argv contains numbers. Try passing strings instead (`node main.js abc def ghi` via the argv field) and watch Run succeed even though the suite still fails.

Task

  1. Replace the body of leftpad in left-pad.js with return str.padStart(length, paddingCharacter);
  2. Click ▶ Run. The default argv (numbers) will crash — (124).padStart is not a function.
  3. Now type abc def ghi into the argv field and click ▶ Run again. The string argv works.
  4. Click ✓ Test My Work. Six tests should pass, three should fail (numeric / null / undefined).
  5. Read the failing tests’ names and error messages. Confirm they’re the non-string-input cases.
  6. Step 3 will fix it — but first, understand the break.
Starter files
left-pad.js
// The original left-pad — soon to be replaced with one line.
//
// 🛠️ TASK: Replace the body of leftpad with:
//
//     return str.padStart(length, paddingCharacter);
//
// Then click ▶ Run (notice non-string argv crashes) and ✓ Test My Work
// (6 tests pass, 3 fail).

module.exports = leftpad;
function leftpad (str, length, paddingCharacter = ' ') {
  str = String(str);
  length = length - str.length;
  if (length < 1) {
    return str;
  }
  str = paddingCharacter.repeat(length) + str;
  return str;
}
main.js
// main.js — same driver as step 1.

const leftpad = require('./left-pad.js');

const argv = process.argv.slice(2);
const numbers = argv.length > 0 ? argv : [124, 1, 90093, 9193];

const len = 5;
for (const num of numbers) {
  console.log(leftpad(num, len, ' '));
}
__tests__/leftpad_test.js
// Same Jest-style contract tests as step 1 — unchanged.
// The point is to watch the test suite respond to a change in
// left-pad.js.

let __passed = 0, __failed = 0;
function describe(name, fn) { console.log(name); fn(); }
function test(name, fn) {
  try { fn(); console.log('' + name); __passed++; }
  catch (e) { console.log('' + name); console.log('    ' + e.message); __failed++; }
}
function expect(actual) {
  return { toBe(expected) {
    if (!Object.is(actual, expected)) {
      throw new Error('Expected ' + JSON.stringify(expected) + ', got ' + JSON.stringify(actual));
    }
  }};
}

const leftpad = require('../left-pad.js');

describe('leftpad', () => {

  test('should pad a short string with spaces to the specified length', () => {
    expect(leftpad('foo', 5)).toBe('  foo');
  });

  test('should return the original string if its length is equal to the target length', () => {
    expect(leftpad('foobar', 6)).toBe('foobar');
  });

  test('should return the original string if its length is greater than the target length', () => {
    expect(leftpad('foobar', 3)).toBe('foobar');
  });

  test('should pad with a custom character', () => {
    expect(leftpad('foo', 5, '0')).toBe('00foo');
  });

  test('should return the original string when target length is 0', () => {
    expect(leftpad('foo', 0)).toBe('foo');
  });

  test('should handle numeric input by converting it to a string', () => {
    expect(leftpad(123, 5, '0')).toBe('00123');
  });

  test('should handle null input by converting it to the string "null"', () => {
    expect(leftpad(null, 6)).toBe('  null');
  });

  test('should handle undefined input by converting it to the string "undefined"', () => {
    expect(leftpad(undefined, 12)).toBe('   undefined');
  });

  test('should pad an empty string', () => {
    expect(leftpad('', 4, 'x')).toBe('xxxx');
  });
});

console.log('\n' + __passed + ' passed, ' + __failed + ' failed');
process.exit(__failed > 0 ? 1 : 0);
package.json
{
  "name": "left-pad-lecture",
  "version": "1.0.0",
  "scripts": {
    "test": "jest",
    "dev": "node main.js"
  },
  "devDependencies": {
    "jest": "^30.2.0"
  }
}
3

Restore the Contract — `String(str).padStart(length, paddingCharacter)`

Why this matters

The failing tests in step 2 weren’t a flaw in padStart — they were 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, length, paddingCharacter) {
  return String(str).padStart(length, paddingCharacter);
}

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(123, 5, '0') runs as String(123).padStart(5, '0')'123'.padStart(5, '0')'00123'. All nine tests pass.

The before/after, side by side

// BEFORE — the famous eleven lines (well, eight here).
function leftpad (str, length, paddingCharacter = ' ') {
  str = String(str);
  length = length - str.length;
  if (length < 1) {
    return str;
  }
  str = paddingCharacter.repeat(length) + str;
  return str;
}

// AFTER — one line of ES2017.
function leftpad (str, length, paddingCharacter) {
  return String(str).padStart(length, paddingCharacter);
}

✏️ Predict before you run

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

  • (a) No — padStart(length, undefined) pads with 'undefined' repeated.
  • (b) No — padStart(length, 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 (`pad a short string with spaces to the specified length`) passes without us writing `paddingCharacter = ' '` in the function signature. 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 left-pad.js, replace the body of leftpad with return String(str).padStart(length, paddingCharacter);
  2. Click ▶ Run. The number argv now works again (numbers are coerced to strings).
  3. Click ✓ Test My Work.
  4. Confirm all nine tests pass.
  5. Look at left-pad.js once more — that’s the entire module. Sit with that.
Starter files
left-pad.js
// 🛠️ TASK: Replace the body of leftpad with:
//
//     return String(str).padStart(length, paddingCharacter);
//
// Then click ▶ Run and ✓ Test My Work. All nine tests should pass.

module.exports = leftpad;
function leftpad (str, length, paddingCharacter) {
  return str.padStart(length, paddingCharacter);  // still naive — fix me!
}
main.js
// main.js — same driver as step 1.

const leftpad = require('./left-pad.js');

const argv = process.argv.slice(2);
const numbers = argv.length > 0 ? argv : [124, 1, 90093, 9193];

const len = 5;
for (const num of numbers) {
  console.log(leftpad(num, len, ' '));
}
__tests__/leftpad_test.js
// Same Jest-style contract tests as steps 1 and 2 — unchanged.
// The point is to watch the same suite confirm correctness
// across three different implementations.

let __passed = 0, __failed = 0;
function describe(name, fn) { console.log(name); fn(); }
function test(name, fn) {
  try { fn(); console.log('' + name); __passed++; }
  catch (e) { console.log('' + name); console.log('    ' + e.message); __failed++; }
}
function expect(actual) {
  return { toBe(expected) {
    if (!Object.is(actual, expected)) {
      throw new Error('Expected ' + JSON.stringify(expected) + ', got ' + JSON.stringify(actual));
    }
  }};
}

const leftpad = require('../left-pad.js');

describe('leftpad', () => {

  test('should pad a short string with spaces to the specified length', () => {
    expect(leftpad('foo', 5)).toBe('  foo');
  });

  test('should return the original string if its length is equal to the target length', () => {
    expect(leftpad('foobar', 6)).toBe('foobar');
  });

  test('should return the original string if its length is greater than the target length', () => {
    expect(leftpad('foobar', 3)).toBe('foobar');
  });

  test('should pad with a custom character', () => {
    expect(leftpad('foo', 5, '0')).toBe('00foo');
  });

  test('should return the original string when target length is 0', () => {
    expect(leftpad('foo', 0)).toBe('foo');
  });

  test('should handle numeric input by converting it to a string', () => {
    expect(leftpad(123, 5, '0')).toBe('00123');
  });

  test('should handle null input by converting it to the string "null"', () => {
    expect(leftpad(null, 6)).toBe('  null');
  });

  test('should handle undefined input by converting it to the string "undefined"', () => {
    expect(leftpad(undefined, 12)).toBe('   undefined');
  });

  test('should pad an empty string', () => {
    expect(leftpad('', 4, 'x')).toBe('xxxx');
  });
});

console.log('\n' + __passed + ' passed, ' + __failed + ' failed');
process.exit(__failed > 0 ? 1 : 0);
package.json
{
  "name": "left-pad-lecture",
  "version": "1.0.0",
  "scripts": {
    "test": "jest",
    "dev": "node main.js"
  },
  "devDependencies": {
    "jest": "^30.2.0"
  }
}