Problem solving · stretch-tier · asked at Amazon, Apple, Google, Meta, Microsoft, Netflix

Unicode String Length

mediumdebugging

Problem

A profile service validates display names before writing them to a downstream store, and produces a short preview for list views:

MAX_NAME = 50   # the profile store rejects names longer than 50


def validate_display_name(name: str) -> str:
    name = name.strip()
    if len(name) > MAX_NAME:
        raise ValueError("display name too long")
    return name


def preview(name: str, width: int = 12) -> str:
    return name[:width] + "…" if len(name) > width else name

Three bug reports arrived in the same week:

  1. Some names pass validation and are then rejected by the store with "value exceeds 50 bytes". All of them contain non-Latin characters or emoji.
  2. preview sometimes renders a broken glyph or a stray box at the cut point.
  3. Two users report that a duplicate-name check says "José" is available even though a user called "José" already exists.

Explain all three with one underlying cause and fix the code.

Examples

  • "Zoë" typed on one keyboard is "Zoë" (3 code points, 4 UTF-8 bytes); the same name pasted from another app can be "Zoë" (4 code points, 5 bytes): e followed by a combining diaeresis. They print identically, len differs, and == is False.
  • "👨‍👩‍👧" (family emoji) has len == 5 (three people joined by two zero-width joiners) and is 18 UTF-8 bytes. A name of 10 such emoji has len == 50, passes the check, and is 180 UTF-8 bytes.
  • preview("Ana 🇧🇷 Silva", 5) → "Ana 🇧" then "…". The flag is two regional-indicator code points; cutting after the first leaves a lone indicator that renders as a boxed letter.

Hints

  1. len(s) counts code points. Name three other units a "length" could be measured in, and decide which one each bug report is about.
  2. Compare "é".encode() for the precomposed and decomposed spellings. Then look at unicodedata.normalize.
  3. A safe byte-truncation must never cut in the middle of a multi-byte sequence. What does bytes.decode(errors="ignore") do with a trailing partial sequence?

Last reviewed 8 September 2026