JavaScript
Coding Tips
Web Development
Programming
Number Formatting

How to output numbers with leading zeros in JavaScript?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Leading zeros in JavaScript are a string formatting operation, not a numeric one. JavaScript numbers do not store or display leading zeros, so the approach is always to convert to a string and pad it to the desired width. The modern solution is String(value).padStart(width, "0"), which is built into every current JavaScript engine and covers the vast majority of use cases cleanly.

padStart: The Standard Approach

String.prototype.padStart() was introduced in ES2017 and is the canonical way to add leading zeros:

javascript
const orderNumber = 42;
const padded = String(orderNumber).padStart(6, "0");
console.log(padded); // "000042"

The method takes two arguments: the target length and the padding character. If the string is already at or beyond the target length, padStart returns it unchanged:

javascript
console.log(String(123456).padStart(6, "0")); // "123456"
console.log(String(1234567).padStart(6, "0")); // "1234567" (no truncation)

This is important: padStart never truncates. If the number has more digits than the target width, it comes through unmodified.

Writing a Reusable Helper

When the same formatting rule appears in multiple places, wrap it in a function:

javascript
1function zeroPad(value, width) {
2  return String(value).padStart(width, "0");
3}
4
5console.log(zeroPad(7, 3));    // "007"
6console.log(zeroPad(42, 5));   // "00042"
7console.log(zeroPad(9999, 3)); // "9999" (no truncation)

This keeps the intent clear and avoids scattering padStart calls with magic numbers throughout the codebase.

Formatting Dates and Times

The most common real-world use case for leading zeros is date and time formatting. Hours, minutes, seconds, months, and days are conventionally displayed as two-digit values:

javascript
1function formatTime(date) {
2  const h = String(date.getHours()).padStart(2, "0");
3  const m = String(date.getMinutes()).padStart(2, "0");
4  const s = String(date.getSeconds()).padStart(2, "0");
5  return `${h}:${m}:${s}`;
6}
7
8function formatDate(date) {
9  const y = date.getFullYear();
10  const m = String(date.getMonth() + 1).padStart(2, "0");
11  const d = String(date.getDate()).padStart(2, "0");
12  return `${y}-${m}-${d}`;
13}
14
15const now = new Date();
16console.log(formatDate(now)); // "2026-06-18"
17console.log(formatTime(now)); // "09:05:03"

This replaces the older pattern of writing if (value < 10) return "0" + value scattered across every formatting call.

Formatting File Names and IDs

Sequential file names and identifiers often require fixed-width numbering for correct alphabetical sorting:

javascript
1// Generate image filenames: frame_001.png through frame_100.png
2for (let i = 1; i <= 100; i++) {
3  const filename = `frame_${String(i).padStart(3, "0")}.png`;
4  // frame_001.png, frame_002.png, ..., frame_100.png
5}
6
7// Invoice IDs with year prefix
8function invoiceId(year, sequence) {
9  return `INV-${year}-${String(sequence).padStart(5, "0")}`;
10}
11console.log(invoiceId(2026, 42)); // "INV-2026-00042"

Without leading zeros, file explorers sort frame_10.png before frame_2.png because string comparison works character by character.

Handling Negative Numbers

A straightforward padStart on a negative number places zeros before the minus sign, which is almost never the desired result:

javascript
console.log(String(-7).padStart(4, "0")); // "0-7" (wrong)

Handle the sign separately:

javascript
1function zeroPadSigned(value, width) {
2  if (value < 0) {
3    return "-" + String(Math.abs(value)).padStart(width, "0");
4  }
5  return String(value).padStart(width, "0");
6}
7
8console.log(zeroPadSigned(-7, 3));  // "-007"
9console.log(zeroPadSigned(7, 3));   // "007"
10console.log(zeroPadSigned(-42, 4)); // "-0042"

Note that the width parameter here refers to the digit width, not including the sign. Adjust if your use case requires a fixed total width including the sign character.

Legacy Approach: Concatenate and Slice

Before padStart existed, the standard workaround was to prepend zeros and slice from the end:

javascript
1function zeroPadLegacy(value, width) {
2  return ("0".repeat(width) + value).slice(-width);
3}
4
5console.log(zeroPadLegacy(42, 5)); // "00042"

This works but is harder to read at a glance. It also has a subtle bug with negative numbers and values longer than width. Use padStart in any codebase that targets ES2017 or later, which includes all current browsers and Node.js versions from 8 onward.

Using Intl.NumberFormat for Locale-Aware Formatting

For more complex numeric formatting, the Intl.NumberFormat API provides built-in support for minimum integer digits:

javascript
1const formatter = new Intl.NumberFormat("en-US", {
2  minimumIntegerDigits: 5,
3  useGrouping: false,
4});
5
6console.log(formatter.format(42));    // "00042"
7console.log(formatter.format(12345)); // "12345"

Setting useGrouping: false prevents the formatter from inserting commas or other grouping separators. This approach is useful when you are already using Intl for currency or percentage formatting and want consistent number presentation.

TypeScript Type Safety

In TypeScript projects, you may want to distinguish between raw numbers and formatted strings at the type level:

typescript
1type PaddedString = string & { readonly __brand: "padded" };
2
3function zeroPad(value: number, width: number): PaddedString {
4  return String(value).padStart(width, "0") as PaddedString;
5}
6
7// Prevents accidentally passing a raw number where a padded string is expected
8function renderOrderId(id: PaddedString): string {
9  return `Order #${id}`;
10}
11
12const id = zeroPad(42, 6);
13renderOrderId(id); // works
14// renderOrderId("42"); // type error

This is a lightweight branding pattern that prevents mixing padded and unpadded values.

Comparison of Methods

MethodBrowser supportHandles negativesTruncatesReadability
padStartES2017+ (all modern)Needs manual handlingNoHigh
Concatenate + sliceAll versionsBreaks silentlyYes (can clip digits)Medium
Intl.NumberFormatAll modernBuilt-inNoHigh
Custom if checksAll versionsManualNoLow

Common Pitfalls

  • Expecting JavaScript numbers to store leading zeros. 007 as a number literal is 7. Leading zeros are purely a string presentation concern.
  • Padding a negative number without separating the sign. String(-7).padStart(4, "0") produces "0-7", not "-007".
  • Converting a padded string back to a number. Number("00042") returns 42, stripping the leading zeros. Keep padded values as strings through their entire lifecycle.
  • Using a fixed width that the data will eventually outgrow. If you pad invoice numbers to 4 digits, the 10,000th invoice breaks the format. Choose a width with growth margin.
  • Using the legacy slice approach in modern codebases. It is harder to read, harder to maintain, and has edge-case bugs that padStart avoids.

Summary

  • Leading zeros in JavaScript are created by converting numbers to strings and padding them. Numbers themselves cannot hold leading zeros.
  • String(value).padStart(width, "0") is the standard modern solution, available in ES2017 and all current runtimes.
  • Handle negative numbers by separating the sign before padding the absolute value.
  • Intl.NumberFormat with minimumIntegerDigits provides an alternative when locale-aware formatting is already in use.
  • Always keep padded values as strings. Converting back to a number discards the padding.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.