Talentd
FleetCode
Make and follow sheets.Sign in

Unique Email Addresses solution and explanation

EasyArrayHash TableString7 min read67.9% acceptanceUpdated 9 Apr 2026
Unique Email Addresses - Two Solutions - Leetcode 929 PythonNeetCode · 11:38 · 26K viewsWatch on YouTube
9 more videos on this problem
  1. Unique Email Addresses - Two Solutions - Leetcode 929 PythonNeetCode · 11:38 · 26K views
  2. LeetCode Unique Email Addresses Solution Explained - JavaNick White · 6:14 · 11K views
  3. 929. Unique Email Addresses (Leetcode Easy)Programming Live with Larry · 13:30 · 4K views
  4. Unique Email Addresses - LeetCode 929 PythonDeepti Talesra · 6:01 · 1K views
  5. Unique Email Addresses | Leetcode 929 | Live coding session 🔥🔥🔥Coding Decoded · 6:44 · 1K views
  6. Leetcode - Unique Email Addresses (Python)Timothy H Chang · 3:16 · 1K views
  7. 929. Unique Email Addresses | LEETCODE EASY | LOGIC | INTERVIEW PROBLEMcode Explainer · 6:52 · 793 views
  8. Unique Email Addresses | TOP GOOGLE INTERVIEW QUESTION | LEETCODE | Keep Learning EverdayNikash Kumar · 7:11 · 710 views
  9. How to Solve "929 Unique Email Addresses" on LeetCode?Becoming A Better Dev (with David) · 6:58 · 657 views
  10. LeetCode 929. Unique Email Addresses | Interview Preparation | English | code iocode io - English · 21:36 · 624 views

The problem

Every valid email consists of a local name and a domain name, separated by the '@' sign. Besides lowercase letters, the email may contain one or more '.' or '+'.

  • For example, in "alice@leetcode.com", "alice" is the local name, and "leetcode.com" is the domain name.

If you add periods '.' between some characters in the local name part of an email address, mail sent there will be forwarded to the same address without dots in the local name. Note that this rule does not apply to domain names.

  • For example, "alice.z@leetcode.com" and "alicez@leetcode.com" forward to the same email address.

If you add a plus '+' in the local name, everything after the first plus sign will be ignored. This allows certain emails to be filtered. Note that this rule does not apply to domain names.

  • For example, "m.y+name@email.com" will be forwarded to "my@email.com".

It is possible to use both of these rules at the same time.

Given an array of strings emails where we send one email to each emails[i], return the number of different addresses that actually receive mails.

Example 1:

Input: emails = ["test.email+alex@leetcode.com","test.e.mail+bob.cathy@leetcode.com","testemail+david@lee.tcode.com"]
Output: 2
Explanation: "testemail@leetcode.com" and "testemail@lee.tcode.com" actually receive mails.

Example 2:

Input: emails = ["a@leetcode.com","b@leetcode.com","c@leetcode.com"]
Output: 3

Constraints:

  • 1 <= emails.length <= 100
  • 1 <= emails[i].length <= 100
  • emails[i] consist of lowercase English letters, '+', '.' and '@'.
  • Each emails[i] contains exactly one '@' character.
  • All local and domain names are non-empty.
  • Local names do not start with a '+' character.
  • Domain names end with the ".com" suffix.
  • Domain names must contain at least one character before ".com" suffix.

Approach overview

Problem Overview: You receive a list of email addresses. Certain normalization rules apply to the local name: dots (.) are ignored and everything after a plus (+) is discarded before the @. The task is to apply these rules and count how many unique addresses actually receive mail.

Approach 1: Using Set to Handle Unique Emails (O(n * m) time, O(n * m) space)

Iterate through each email and normalize it before counting. Split the address into local and domain around the @. Inside the local part, remove everything after the first +, then delete all dots. Reconstruct the normalized address and insert it into a set. Because a set stores only unique elements, duplicate normalized emails are automatically ignored. The algorithm scans each character of every email once, giving O(n * m) time where n is the number of emails and m is the average length.

This approach relies heavily on fast membership checks from a hash table. Sets provide average O(1) insertion and lookup, making them ideal for uniqueness problems. The logic is straightforward and easy to implement in any language using basic string operations.

Approach 2: Email Transformation with String Array Manipulation (O(n * m) time, O(n * m) space)

This method explicitly transforms the email step by step using string arrays. First split the email using @ into two parts. Convert the local name into a character array or iterate through it with a pointer. Stop processing when a + is encountered and skip dots while building the cleaned local name. Finally combine the cleaned local name with the domain and insert the result into a set to track uniqueness.

The key difference is the manual transformation process. Instead of chained string operations, you iterate through characters and build the normalized string incrementally. This gives tighter control over processing and can slightly reduce intermediate allocations in some languages. The complexity remains O(n * m) because each character is processed at most once.

Both solutions treat the problem as normalization followed by deduplication. The normalization stage applies deterministic rules, and the set guarantees uniqueness. The problem primarily tests comfort with array iteration, string parsing, and hash-based collections.

Recommended for interviews: The set-based normalization approach is what interviewers expect. It demonstrates clean thinking: transform each email once, then rely on a hash set for uniqueness. Brute reasoning about duplicates shows understanding, but recognizing that normalization + hashing solves the problem efficiently shows stronger problem‑solving skill.

Approach 1: Using Set to Handle Unique Emails

In this approach, we'll use a set to store unique email addresses. We'll iterate over each email, split it into local and domain parts, then manipulate the local part by removing any characters after the first '+' and all periods '.' before rejoining with the domain. Finally, we'll add the standardized email to the set. The size of the set gives the count of unique emails.

def num_unique_emails(emails):
    unique_emails = set()
    for email in emails:
        local, domain = email.split('@')
        local = local.split('+')[0].replace('.', '')
        unique_emails.add(f"{local}@{domain}")
    return len(unique_emails)
import java.util.HashSet;
import java.util.Set;

public class UniqueEmail {
    public int numUniqueEmails(String[] emails) {
        Set<String> uniqueEmails = new HashSet<>();
        for (String email : emails) {
            String[] parts = email.split("@");
            String local = parts[0];
            String domain = parts[1];
            int plusIndex = local.indexOf('+');
            if (plusIndex != -1) {
                local = local.substring(0, plusIndex);
            }
            local = local.replaceAll(".", "");
            uniqueEmails.add(local + "@" + domain);
        }
        return uniqueEmails.size();
    }
}
#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

int numUniqueEmails(vector<string>& emails) {
    unordered_set<string> unique_emails;
    for (string email : emails) {
        int at_index = email.find('@');
        string local = email.substr(0, at_index);
        string domain = email.substr(at_index);
        int plus_index = local.find('+');
        if (plus_index != string::npos) {
            local = local.substr(0, plus_index);
        }
        local.erase(remove(local.begin(), local.end(), '.'), local.end());
        unique_emails.insert(local + domain);
    }
    return unique_emails.size();
}
function numUniqueEmails(emails) {
    const uniqueEmails = new Set();
    emails.forEach(email => {
        const [local, domain] = email.split('@');
        const processedLocal = local.split('+')[0].replace(/\./g, '');
        uniqueEmails.add(`${processedLocal}@${domain}`);
    });
    return uniqueEmails.size;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int numUniqueEmails(char **emails, int emailsSize) {
    char *uniqueEmails[100];
    int uniqueCount = 0;

    for (int i = 0; i < emailsSize; i++) {
        char *email = emails[i];
        char local[100] = "";
        char *domain = strchr(email, '@');
        *domain++ = '\0';

        char *plus = strchr(email, '+');
        if (plus != NULL) {
            *plus = '\0';
        }

        for (char *c = email; *c != '\0'; c++) {
            if (*c != '.') {
                strncat(local, c, 1);
            }
        }

        strcat(local, "@");
        strcat(local, domain);

        int isDuplicate = 0;
        for (int j = 0; j < uniqueCount; j++) {
            if (strcmp(uniqueEmails[j], local) == 0) {
                isDuplicate = 1;
                break;
            }
        }
        if (!isDuplicate) {
            uniqueEmails[uniqueCount] = strdup(local);
            uniqueCount++;
        }
    }
    return uniqueCount;
}
using System;
using System.Collections.Generic;

public class UniqueEmail
{
    public int NumUniqueEmails(string[] emails)
    {
        HashSet<string> uniqueEmails = new HashSet<string>();
        foreach (string email in emails)
        {
            var parts = email.Split('@');
            string local = parts[0];
            string domain = parts[1];
            int plusIndex = local.IndexOf('+');
            if (plusIndex != -1)
            {
                local = local.Substring(0, plusIndex);
            }
            local = local.Replace(".", "");
            uniqueEmails.Add(local + "@" + domain);
        }
        return uniqueEmails.Count;
    }
}

In Python, we use a set to hold unique email addresses. We split each email by '@' to get local and domain parts. The local part is then split at the '+' to ignore everything beyond it and replaced '.' with ''. The processed local part is then combined with the domain part before adding to the set. The number of unique emails is the size of the set.

Complexity

Time Complexity: O(N * M) where N is the number of emails and M is the maximum length of an email address.
Space Complexity: O(N) due to the storage of unique emails in a set.

Try this approach in the editor

Approach 2: Email Transformation with String Array Manipulation

In this approach, instead of transforming emails directly using a set, we'll utilize arrays to manipulate the email parts. We'll maintain a boolean array for discovered unique emails and an index array to track up to where the email is unique. This efficient tracking helps us determine unique addresses without full storage, but more focused on transformations directly performed within the iteration loop.

def num_unique_emails(emails):
    seen = set()
    for email in emails:
        local, domain = email.split('@')
        local = local.split('+')[0].replace('.', '')
        standardized_email = local + '@' + domain
        if standardized_email not in seen:
            seen.add(standardized_email)
    return len(seen)
import java.util.*;

public class UniqueEmail {
    public int numUniqueEmails(String[] emails) {
        Set<String> seenEmails = new HashSet<>();
        Arrays.stream(emails).forEach(email -> {
            StringBuilder localPart = new StringBuilder();
            StringBuilder domainPart = new StringBuilder();
            boolean atSymbol = false;
            boolean plusSymbol = false;
            for (char c : email.toCharArray()) {
                if (c == '@') {
                    atSymbol = true;
                }
                if (atSymbol) {
                    domainPart.append(c);
                } else {
                    if (c == '+') {
                        plusSymbol = true;
                    } else if (c != '.' && !plusSymbol) {
                        localPart.append(c);
                    }
                }
            }
            seenEmails.add(localPart.toString() + domainPart.toString());
        });
        return seenEmails.size();
    }
}
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>

using namespace std;

int numUniqueEmails(vector<string>& emails) {
    unordered_set<string> seenEmails;
    for (auto& email : emails) {
        string local, domain;
        bool at = false, plus = false;
        for (char c : email) {
            if (c == '@') {
                at = true;
                if (!plus) {
                    seenEmails.insert(local + domain);
                }
            }
            if (at) {
                domain += c;
            } else {
                if (c == '+'){
                    plus = true;
                } else if (c != '.' && !plus) {
                    local += c;
                }
            }
        }
    }
    return seenEmails.size();
}
function numUniqueEmails(emails) {
    let uniqueEmails = new Set();
    emails.forEach(email => {
        let local = "", domain = "";
        let atSymbol = false, plusSymbol = false;
        for (let char of email) {
            if (char === '@') {
                atSymbol = true;
            }
            if (atSymbol) {
                domain += char;
            } else {
                if (char === '+') {
                    plusSymbol = true;
                } else if (char !== '.' && !plusSymbol) {
                    local += char;
                }
            }
        }
        uniqueEmails.add(local + domain);
    });
    return uniqueEmails.size;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int numUniqueEmails(char **emails, int emailsSize) {
    char *uniqueEmails[100];
    int uniqueCount = 0;

    for (int i = 0; i < emailsSize; i++) {
        char local[100] = "";
        char domain[100] = "";
        int j = 0, k = 0, plus = 0, seenAt = 0;
        while (emails[i][j] != '\0') {
            char curChar = emails[i][j++];
            if (curChar == '@') {
                seenAt = 1;
            }
            if (seenAt) {
                domain[k++] = curChar;
            } else {
                if (curChar == '+') {
                    plus = 1;
                } else if (curChar != '.' && !plus) {
                    strcat(local, (const char[]){curChar, 0});
                }
            }
        }
        domain[k] = '\0';

        int isDuplicate = 0;
        char combined[200];
        sprintf(combined, "%s%s", local, domain);
        for (int z = 0; z < uniqueCount; z++) {
            if (strcmp(uniqueEmails[z], combined) == 0) {
                isDuplicate = 1;
                break;
            }
        }
        if (!isDuplicate) {
            uniqueEmails[uniqueCount++] = strdup(combined);
        }
    }
    return uniqueCount;
}
using System;
using System.Collections.Generic;

public class UniqueEmail
{
    public int NumUniqueEmails(string[] emails)
    {
        HashSet<string> seenEmails = new HashSet<string>();
        foreach (string email in emails)
        {
            var local = new System.Text.StringBuilder();
            var domain = new System.Text.StringBuilder();
            bool atSymbol = false, plusSymbol = false;
            foreach (char c in email)
            {
                if (c == '@')
                {
                    atSymbol = true;
                }
                if (atSymbol)
                {
                    domain.Append(c);
                }
                else
                {
                    if (c == '+')
                    {
                        plusSymbol = true;
                    }
                    else if (c != '.' && !plusSymbol)
                    {
                        local.Append(c);
                    }
                }
            }
            seenEmails.Add(local.ToString() + domain.ToString());
        }
        return seenEmails.Count;
    }
}

This is similar to Approach 1, but a slight variation in explanation to describe transformations as stepwise and using string-based mutation directly. The Python simplicity helps accomplish similar efficiency with identical methods.

Complexity

Time Complexity: O(N * M), since per email processed once fully.
Space Complexity: O(N), enabling efficient tracking of unique addresses.

Try this approach in the editor

Approach 3: Hash Table

We can use a hash table s to store all unique email addresses. Then, we traverse the array emails. For each email address, we split it into the local part and the domain part. We process the local part by removing all dots and ignoring characters after a plus sign. Finally, we concatenate the processed local part with the domain part and add it to the hash table s.

In the end, we return the size of the hash table s.

The time complexity is O(L), and the space complexity is O(L), where L is the total length of all email addresses.

function numUniqueEmails(emails: string[]): number {
    const s = new Set<string>();
    for (const email of emails) {
        const [local, domain] = email.split('@');
        let t = '';
        for (const c of local) {
            if (c === '.') {
                continue;
            }
            if (c === '+') {
                break;
            }
            t += c;
        }
        s.add(t + '@' + domain);
    }
    return s.size;
}
func numUniqueEmails(emails []string) int {
	s := make(map[string]struct{})
	for _, email := range emails {
		parts := strings.Split(email, "@")
		local := parts[0]
		domain := parts[1]
		var t strings.Builder
		for _, c := range local {
			if c == '.' {
				continue
			}
			if c == '+' {
				break
			}
			t.WriteByte(byte(c))
		}
		s[t.String()+"@"+domain] = struct{}{}
	}
	return len(s)
}
use std::collections::HashSet;

impl Solution {
    pub fn num_unique_emails(emails: Vec<String>) -> i32 {
        let mut s = HashSet::new();

        for email in emails {
            let parts: Vec<&str> = email.split('@').collect();
            let local = parts[0];
            let domain = parts[1];
            let mut t = String::new();
            for c in local.chars() {
                if c == '.' {
                    continue;
                }
                if c == '+' {
                    break;
                }
                t.push(c);
            }
            s.insert(format!("{}@{}", t, domain));
        }

        s.len() as i32
    }
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
Using Set to Handle Unique Emails

Time Complexity: O(N * M) where N is the number of emails and M is the maximum length of an email address.
Space Complexity: O(N) due to the storage of unique emails in a set.

Email Transformation with String Array Manipulation

Time Complexity: O(N * M), since per email processed once fully.
Space Complexity: O(N), enabling efficient tracking of unique addresses.

Hash Table–

Complexity analysis

ApproachTimeSpaceWhen to Use
Using Set to Handle Unique EmailsO(n * m)O(n * m)Best general solution when you want simple normalization and fast uniqueness checks
Email Transformation with String Array ManipulationO(n * m)O(n * m)Useful when implementing manual parsing or minimizing repeated string operations

Frequently asked questions

Is Unique Email Addresses easy or hard?

Unique Email Addresses is classified as an Easy problem on LeetCode. The challenge is understanding the transformation rules for the local part of the email and implementing them correctly. Once normalized, counting unique addresses with a hash set is straightforward.

Unique Email Addresses Python/Java solution

In Python, use a set and string methods like split(), replace(), and slicing to normalize the local name before adding it to the set. In Java, use HashSet<String> and String.split() or manual character iteration. Both implementations follow the same normalization rules and achieve O(n * m) complexity.

How to solve Unique Email Addresses in O(n)?

Treat the problem as normalization followed by deduplication. For each email, split around '@', remove dots from the local name, ignore characters after '+', and rebuild the address. Insert the normalized email into a hash set. Since each email is scanned once, the complexity becomes O(n * m), which is linear relative to total characters processed.

What is the best approach for Unique Email Addresses?

The best approach normalizes each email and stores the result in a hash set. Remove dots from the local name, ignore everything after the first plus sign, then combine it with the domain. Insert the normalized address into a set to ensure uniqueness. This method runs in O(n * m) time where n is the number of emails and m is the average email length.

Is Unique Email Addresses asked at Google/Amazon/Meta?

Unique Email Addresses is commonly seen in coding interview preparation lists and practice sets. It appears in screening rounds and practice platforms because it tests string manipulation and hash set usage. The difficulty is easy, but it checks whether candidates handle parsing rules carefully.

What data structure is used in Unique Email Addresses?

A hash set is the primary data structure. After normalizing each email address, the set stores unique entries and automatically removes duplicates. The approach relies on constant‑time average insertion and lookup provided by hash tables.

What is the time complexity of Unique Email Addresses?

The typical solution runs in O(n * m) time. Each email is processed once and each character in the email is examined during normalization. Space complexity is also O(n * m) because the set stores up to n normalized email strings.

Comments

Now write it yourselfClose this page and solve Unique Email Addresses in the editor, without looking.
Practise Unique Email Addresses