padStart()
ES2017+Pads the beginning of a string with another string, repeated and truncated as needed, until the result reaches a target length, then returns that new string. If the original already meets or exceeds the target length it is returned unchanged, never truncated. The default pad is a space; added in ES2017.
Syntax
string.padStart(targetLength, padString)Parameters
targetLength number The length of the resulting string
padString string optionalThe string to pad with (default: space)
Return Value
The padded string
Examples
const num = '5';
console.log(num.padStart(3, '0'));
console.log('abc'.padStart(6, '123')); 📌 When to Use
Use padStart() whenever values must line up from the right or reach a fixed width: zero-padding clock components so 9:05 renders as 09:05, formatting invoice and ticket numbers like 'INV-00042', right-aligning numbers in console tables and logs, generating fixed-width records for legacy file formats, and displaying binary or hexadecimal values with leading zeros, as in byte.toString(2).padStart(8, '0'). The mental model to internalize is that targetLength is the total desired length of the result, not the amount of padding added; the method computes the deficit itself and adds nothing when the input is already long enough. That no-truncation guarantee makes it safe to apply unconditionally, but it also means over-long input passes through untouched, so enforce maximum widths separately if a layout depends on them. Because it is a string method, numbers must be converted first with String() or toString(), which is also the natural place to choose a radix. The pad string may be multiple characters and is truncated to fit, enabling patterns like dot leaders. One Unicode caveat: lengths count UTF-16 code units, so emoji in either the input or the pad break visual alignment. For locale-aware number formatting with grouping or decimals, prefer Intl.NumberFormat or toFixed(); padStart() is for visual width, not numeric semantics.
⚠️ Common Mistakes
Reading targetLength as pad count: '5'.padStart(3, '0') yields '005', a three-character result, not '0005'. Developers expecting 'add three zeros' write widths that are off by the content length; the parameter is the final total length.
Padding a number without converting it: numbers have no padStart method, so calling it directly on a numeric value throws a TypeError. Convert first with String(value) or value.toString(radix), which is also where the base for binary or hex display is chosen.
Expecting truncation of over-long input: padStart() never shortens anything, so a five-digit id passed to a width-four format comes out five characters long and silently breaks column alignment downstream. Enforce maximums explicitly with slice() when the layout demands them.
Losing the sign position when zero-padding negatives: String(-5).padStart(4, '0') gives '00-5', because the sign is just a character. Format the absolute value and re-attach the sign, or use Intl.NumberFormat with minimumIntegerDigits, when negative values are possible.
Assuming code-unit length equals visual width: an emoji counts as two units but renders roughly double-width in terminals, and CJK characters count one unit but render wide, so padded columns containing such text misalign even though the code is 'correct'.
Multi-character pads and expecting whole repetitions: the pad string is truncated to exactly fill the deficit, so 'abc'.padStart(6, '123') is '123abc' but a deficit of two takes just '12'; patterns that must not be cut mid-unit need explicit assembly.
✅ Best Practices
Convert numbers deliberately at the call site, as in String(minutes).padStart(2, '0'), keeping the conversion and the radix choice visible next to the formatting.
Name your widths: a FIELD_WIDTHS constant or configuration object beats magic numbers scattered through padding calls, especially for fixed-width file formats where widths are contractual.
Treat padStart() as display formatting only: store raw numbers and identifiers, pad at the presentation edge, and never parse padded strings back by position when you control the original data.
Handle signs and decimals before padding, formatting the absolute value and re-attaching the sign, or reach for Intl.NumberFormat with minimumIntegerDigits, which handles these cases natively.
Pair with slice() when a hard column width is required, padding first then slicing, so both under-long and over-long inputs land at exactly the same width.
Prefer padStart(8, '0') over hand-rolled repeat-and-concatenate padding; it is clearer and already handles the already-long-enough case that manual arithmetic tends to get wrong.
⚡ Performance Notes
padStart() allocates one result string of the target length and fills the deficit by repeating the pad string, so its cost is linear in the output size and effectively negligible at the scales where padding makes sense, such as clock digits, ids, and console columns. When the input already meets the target length, implementations return the original string with no allocation at all, which makes unconditional padding calls cheap in the common already-formatted case. It comfortably beats hand-rolled equivalents built from repeat() plus concatenation, which allocate intermediate strings and re-implement the clamp logic. In bulk formatting, such as rendering thousands of table rows, the padding calls are dwarfed by the surrounding string assembly and eventual output; if profiling ever flags formatting, batch rows into an array and join once rather than micro-optimizing individual pad operations.
🌍 Real World Example
Date/Time and Number Formatting
Time and number formatting, the method's signature use case. formatTime() converts each clock component to a string and pads it to two digits with '0', so 9 hours 5 minutes renders as '09:05' instead of the ragged '9:5'; this exact three-line pattern appears in countless timers, log formatters, and video players. The companion patterns pad invoice numbers to fixed widths for sortable, uniform identifiers like 'INV-00042', and pad binary strings to full eight-bit groups for readable bit dumps. All share one idea: convert the number to a string first, then let padStart() guarantee the total width.
// Format time display (HH:MM:SS)
function formatTime(date) {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}
console.log(formatTime(new Date())); // '09:05:32'
// Generate order/invoice numbers
function generateOrderId(sequence) {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const seq = String(sequence).padStart(5, '0');
return `ORD-${year}${month}${day}-${seq}`;
}
console.log(generateOrderId(42)); // 'ORD-20240115-00042'
// Format binary, octal, or hex numbers
function formatBinary(num, bits = 8) {
return num.toString(2).padStart(bits, '0');
}
console.log(formatBinary(5)); // '00000101'
console.log(formatBinary(255)); // '11111111'
// Align numbers in console output
function printTable(items) {
const maxNameLength = Math.max(...items.map(i => i.name.length));
items.forEach(item => {
const name = item.name.padEnd(maxNameLength);
const price = String(item.price).padStart(8);
const qty = String(item.qty).padStart(4);
console.log(`${name} | $${price} | x${qty}`);
});
}
printTable([
{ name: 'Apple', price: 1.5, qty: 10 },
{ name: 'Banana', price: 0.75, qty: 25 }
]);