Lodash _.pad Issues with Multi-Code Point Emojis

Lodash’s _.pad function relies on standard JavaScript UTF-16 code units to compute string length rather than actual visual glyph representations. When strings contain multi-code point emojis—such as zero-width joiner (ZWJ) sequences, skin-tone modifiers, or country flags—Lodash miscalculates the visual width of the text. This mismatch causes under-padding, asymmetrical centering, and even character corruption if complex emojis are used as the padding characters themselves.

The Root Cause: Code Units vs. Grapheme Clusters

JavaScript strings measure length using 16-bit code units via String.prototype.length. A standard alphanumeric character has a length of 1. However, modern emojis often consist of surrogate pairs, combining marks, and joiners:

Because _.pad(string, length, [chars=' ']) determines required padding by subtracting string.length from the target length, the function operates on raw code units rather than user-perceived characters (grapheme clusters).

Specific Formatting Inconsistencies

1. Severe Under-Padding

When formatting text to align in fixed-width displays or terminal interfaces, _.pad assumes a string with an emoji is significantly wider than it appears visually. For example, if you attempt to pad 👩‍💻 (visual width: 1 or 2 characters; code unit length: 5) to a total length of 8, Lodash calculates:

\[\text{Padding Needed} = 8 - 5 = 3\]

Instead of providing roughly 6 to 7 spaces of padding to reach an apparent length of 8, it only adds 3 spaces. This breaks column alignment in tables and CLI outputs.

2. Asymmetrical and Skewed Centering

_.pad balances padding by distributing the calculated deficit between the left and right sides of the string:

// Desired: visual center in an 8-character slot
_.pad('👩‍💻', 8); 
// Result: " 👩‍💻  " (1 space left, 2 spaces right)

Because the remaining space calculation is skewed by the invisible code units, the visual output fails to be centered and is weighted unevenly across the sides. If the emoji's code unit length meets or exceeds the target length, _.pad adds no padding at all, even if the string visually occupies only one column.

3. Truncated Glyphs and Unicode Corruption

The issue also occurs in reverse when using multi-code point emojis as the fill argument (chars). If the remaining space requires an odd number of code units, Lodash truncates the padding string to fit the target length:

_.pad('test', 7, '😀');

Because 😀 requires 2 code units, filling an odd gap requires splitting the surrogate pair. Lodash slices the string mid-character, leaving an orphaned high surrogate that renders as an unprintable replacement character (``). Similarly, truncating a ZWJ sequence strips modifiers, transforming a combined character into disconnected components (such as an individual person icon followed by an isolated laptop symbol).

Resolving the Inconsistency

To ensure consistent formatting with multi-code point emojis, strings must be measured and padded based on grapheme clusters rather than UTF-16 code units:

  1. Use Intl.Segmenter: Modern JavaScript environments provide Intl.Segmenter to accurately count user-perceived graphemes:
    const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
    const graphemeCount = [...segmenter.segment(str)].length;
  2. Employ Grapheme-Aware Libraries: Utilities like grapheme-splitter or string-width should be used to calculate true visual lengths (especially accounting for full-width characters in terminal outputs) before applying custom padding logic.