Skip to main content

SF-9219 · Scenario · Medium

How would you prevent duplicate records in Salesforce?

✓ Verified by Vikas Singhal · Last reviewed 5/19/2026 · Updated for Spring '26

Duplicate prevention in Salesforce is a layered defense, not a single setting. The interview answer that wins is one that names the layers, knows when each fires, and acknowledges the trade-offs.

The 60-second answer

For most orgs, the answer is: (1) Matching Rule to define what “duplicate” means, (2) Duplicate Rule to decide what happens — block vs warn — and against which user/record-type scope, (3) a Unique flag on the natural-key field for hard database-level enforcement, and (4) a trigger for cross-object or fuzzy logic Duplicate Rules can’t express. For data loads, run a matching-rule report beforehand and use Data Loader’s “Match using external ID” flow.

The four layers and when each fires

LayerWhen it firesWhat it doesLimits
Unique field (database)On insert/updateHard block, all pathsExact-match only, no fuzzy
Validation ruleOn insert/updateHard block, all pathsOnly sees the current record; no SOQL cross-record
Matching + Duplicate RuleOn UI create/clone, optionally on APIWarn or block; allows overrideStandard objects + custom; fuzzy match available
Apex triggerOn any insert/updateAnything you can codeYou own the bulkification

The right answer for a given scenario depends on where the duplicates come from: UI users, API integrations, data loads, or all three.

Layer 1 — Matching Rule + Duplicate Rule

This is the native, no-code answer and the one interviewers expect first.

Matching Rule = “what does duplicate mean here?” — for example, “Account is a duplicate if Name is fuzzy-matched AND Billing City is exact.” Use the standard matching rules for Accounts, Contacts, and Leads; build custom matching rules for custom objects.

Duplicate Rule = “what do we do about it?” — block, allow with alert, or allow with report. You can scope it to certain record types, certain users, or certain conditions.

Matching Rule (Account):
  Name      → Fuzzy Company Name
  AND
  BillingCity → Exact

Duplicate Rule:
  Action on Create → Block + show alert
  Action on Edit   → Allow + show alert
  Record-level security → Enforce sharing rules
  Bypass duplicate alerts → checked for Data Loader profile

Gotcha: Duplicate Rules fire on UI insert by default. To enforce on API too, check “Run duplicate rules when records are saved via API” in the Duplicate Rule. Without this, Bulk API loads silently bypass duplicate detection.

Layer 2 — Unique field at the database level

Mark a custom field as Unique (case-sensitive or insensitive). The platform enforces this at the database tier — no user, no API, no trigger can bypass it. Best for true natural keys: external IDs, SKUs, email addresses on a Contact when business says no two contacts can share one.

Field: External_ID__c   Type: Text(255)   Unique: ✓  External ID: ✓

Gotcha: Unique + External ID together unlock Data Loader’s upsert flow — the loader matches on this key and updates instead of inserting duplicates. This is the cleanest answer for migration scenarios.

Layer 3 — Trigger for cross-object or fuzzy logic

When the dedup logic spans objects (e.g., “no two Opportunities for the same Account in the same month”) or needs SOQL the Matching Rule can’t express, a trigger is the right tool.

trigger PreventDuplicateOpps on Opportunity (before insert, before update) {
    Set<Id> acctIds = new Set<Id>();
    for (Opportunity o : Trigger.new) {
        if (o.AccountId != null) acctIds.add(o.AccountId);
    }

    Map<Id, Set<String>> existingKeys = new Map<Id, Set<String>>();
    for (Opportunity o : [
        SELECT Id, AccountId, Name, CloseDate
        FROM Opportunity
        WHERE AccountId IN :acctIds
        AND   Id NOT IN :Trigger.newMap?.keySet()
    ]) {
        Id k = o.AccountId;
        String monthKey = o.CloseDate.year() + '-' + o.CloseDate.month();
        if (!existingKeys.containsKey(k)) existingKeys.put(k, new Set<String>());
        existingKeys.get(k).add(monthKey + ':' + o.Name);
    }

    for (Opportunity o : Trigger.new) {
        String monthKey = o.CloseDate.year() + '-' + o.CloseDate.month();
        Set<String> keys = existingKeys.get(o.AccountId);
        if (keys != null && keys.contains(monthKey + ':' + o.Name)) {
            o.addError('Duplicate opportunity for this account in this month.');
        }
    }
}

Notes: bulkify (one SOQL outside the loop), exclude records currently being updated (Trigger.newMap.keySet()), and never addError() after DML — do it in before context.

Layer 4 — Pre-load matching for bulk imports

For migrations, prevent duplicates before they enter the system:

  1. Profile the source dataCOUNT(*) GROUP BY email to find dup candidates.
  2. Run Salesforce’s “Find Duplicates” in the Matching Rule report.
  3. Use Data Loader upsert keyed on a Unique External ID — never Insert for migrated data.
  4. Set the “Bypass duplicate alerts” permission for the migration user only if you’ve pre-cleaned the source.

Anti-patterns to avoid

  • Validation rule with SOQL — validation rules can’t query other records. People sometimes attempt fake formulas with VLOOKUP() — it works on Custom Settings only, not records.
  • Trigger that doesn’t exclude the current record set — query existing dups but forget to exclude Trigger.newMap.keySet(); record matches itself on update, fires false positive.
  • Relying only on UI alerts — Bulk API silently bypasses Duplicate Rules unless “Run on API” is checked.
  • Unique field with case sensitivity off when business uses mixed-case identifiers (turns Acme and ACME into one record — usually wrong for SKUs, usually right for emails).
  • Letting trigger errors block a multi-row insert — use addError() per record, not addError() on the trigger.

How to answer in 30 seconds

“Layer the defenses. Native Matching + Duplicate Rules for fuzzy UI dedup, Unique fields with External ID for the database-level guarantee, an Apex trigger for cross-object or SOQL-heavy logic, and pre-load matching for migrations. Make sure Duplicate Rules are flagged to run on API, not just UI.”

How to answer in 2 minutes

Add the trigger pattern (bulkified SOQL, exclude current set), the Data Loader upsert flow keyed on External ID, the API-bypass gotcha on Duplicate Rules, and the difference between a Validation Rule (single-record) and a trigger (cross-record). Mention that Duplicate Rules respect sharing — a duplicate the user can’t see won’t be flagged unless “Enforce sharing rules” is off.

Likely follow-up questions

  • What’s the difference between a Matching Rule and a Duplicate Rule?
  • How would you find duplicates that already exist in production data?
  • Can you write a Validation Rule that prevents duplicates?
  • What happens to duplicate detection in a Bulk API 2.0 load?
  • How would you merge duplicate Accounts programmatically?

Verified against: Salesforce Help — Duplicate Management, Matching Rules, SOAP API — Upsert. Last reviewed 2026-05-19.