DSA Guide
Strings

Unique Email Addresses

Count distinct inboxes by normalising each address, then storing the results in a set

EasyString Normalisation + Set
ArrayHash TableString
Problem #929

Problem Statement

Every email address consists of a local name and a domain name, separated by @. Two rules apply to the local name only:

  1. Dots are ignored. "alice.z@leetcode.com" and "alicez@leetcode.com" are the same inbox.
  2. Everything from the first + onward is ignored. "m.y+name@a.com" forwards to "my@a.com".

Return the number of distinct addresses that actually receive mail.

Input:  ["test.email+alex@leetcode.com",
         "test.e.mail+bob.cathy@leetcode.com",
         "testemail+david@lee.tcode.com"]
Output: 2
Why:    the first two both normalise to "testemail@leetcode.com"
        the third has a DIFFERENT domain: "testemail@lee.tcode.com"

Intuition

Two things are happening: a normalisation step, and a de-duplication step.

The key insight

Convert each address into a canonical form, then put the canonical forms in a set. The set's size is the answer.

This is the same "canonical key" idea as Group Anagrams — turn "are these equivalent?" into "are these identical after normalisation?"

The rules apply to the local name only

The domain must be left completely untouched. "lee.tcode.com" is a different domain from "leetcode.com" — stripping dots there would merge two genuinely distinct addresses and give the wrong answer.

Splitting on @ before doing anything else is what keeps the two halves separate.

Order of operations

Strip the + suffix first, then remove dots. Doing it the other way also works here, but plus-first is safer: it means dots after the + are discarded along with everything else, rather than being processed and then thrown away.

Watch it run

Normalising "test.e.mail+bob.cathy@leetcode.com"
test.e.mail+bob.cathy
@
leetcode.com
Split on the first @. The local name is on the left; the domain on the right stays untouched.
1 / 4
Normalise, then let a set do the counting.

Approach

Split each address on @

Split only on the first @ — the local name is guaranteed not to contain one, but being explicit avoids surprises.

Solution

def num_unique_emails(emails: list[str]) -> int:
    """Count how many distinct inboxes the addresses resolve to."""
    seen: set[str] = set()

    for email in emails:
        local, domain = email.split("@", 1)   # split on the FIRST @ only

        local = local.split("+", 1)[0]        # drop everything after the first +
        local = local.replace(".", "")        # dots in the local name mean nothing

        seen.add(f"{local}@{domain}")         # the domain is never modified

    return len(seen)

`split('@')` without a limit is risky

"a@b@c".split("@") gives three parts, and unpacking into two variables raises an error in Python. Passing maxsplit=1 (or using indexOf in TypeScript) splits on the first @ only and puts everything else in the domain — the behaviour real email parsing expects.

Why a set and not sorting

Sorting the normalised addresses and counting adjacent differences also works, at O(n log n). A hash set answers the same question in O(n) and expresses the intent directly: you care about distinctness, not order.

Complexity Analysis

Time Complexity

O(n × L)

Space Complexity

O(n × L)

Where n is the number of addresses and L is the average address length.

  • Time — each address is scanned a constant number of times during normalisation.
  • Space — the set holds up to n normalised strings.

Edge Cases

On this page