Katkat

Content Safety Filter

WhatnotSoftware Engineer · Technical Screen

Last updated

Tests string tokenization, interval tracking, and Trie-based multi-word pattern matching to redact prohibited phrases from live-stream messages.

Content Safety Filter

In a live commerce platform, buyers and sellers interact in real-time chat during auctions. To prevent fraud and abuse, incoming chat messages must be filtered to mask prohibited terms and phrases before being broadcasted.

Write a function filter_content(message: str, banned_phrases: list[str]) -> str that detects all occurrences of prohibited phrases in message and replaces every alphanumeric character in the matched words with an asterisk (*). Non-alphanumeric characters (punctuation, symbols, whitespace) must be preserved in their original form and positions.

Rules

  • Case-Insensitive: Matching between message and banned_phrases is case-insensitive.
  • Word Boundaries: Matches must align with word boundaries. A word is defined as a maximal contiguous sequence of alphanumeric characters ([a-zA-Z0-9]). Punctuation and whitespace act as delimiters.
  • Multi-Word Phrases: A banned phrase may contain multiple words separated by single spaces. In the message, words in a multi-word phrase can be separated by any sequence of non-alphanumeric characters (whitespace or punctuation).
  • Preservation: Punctuation and spacing must remain intact; only the alphanumeric characters of matched words are masked.
  • Overlaps: If matched phrases overlap, all characters belonging to any matched phrase must be masked.

Function Signature

def filter_content(message: str, banned_phrases: list[str]) -> str:
    pass

Constraints

  • 1 <= len(message) <= 10^5
  • 1 <= len(banned_phrases) <= 5 * 10^4
  • Total words across all banned phrases <= 10^5
  • banned_phrases contains only alphanumeric words separated by single spaces.
  • Target time complexity: O(N + M) where N is the length of message and M is the total character length of banned_phrases.

Examples

Example 1:

message = "Check out this fake giveaway! Don't buy."
banned_phrases = ["fake giveaway", "buy"]
output = filter_content(message, banned_phrases)
# output: "Check out this **** ********! Don't ***."

Explanation: "fake giveaway" matches the two words "fake" and "giveaway". "buy" matches "buy". All alphanumeric characters are replaced with *, preserving whitespace and punctuation.

Example 2:

message = "Scam-alert: Free coins at url!"
banned_phrases = ["scam alert", "free coins"]
output = filter_content(message, banned_phrases)
# output: "****-*****: **** ***** at url!"

Explanation: In "Scam-alert", the hyphen acts as a non-alphanumeric separator, matching the phrase "scam alert".

Edge Cases

  • Partial word matches must not trigger a replacement (e.g., "buy" should not match inside "buyer").
  • Consecutive punctuation or whitespace between words should not disrupt multi-word phrase matching.

Hints

Hint 1

Tokenize the message by extracting each alphanumeric word along with its start and end character indices in the original string.

Hint 2

Build a Trie where each node represents an entire word (case-normalized) rather than a single character, marking nodes where complete banned phrases end.

Hint 3

Iterate through the message tokens, traversing the word-Trie to find all matching phrase spans, and mask the recorded character intervals in the original string.

Follow-up questions

  • How would you adapt your filter to handle deliberate obfuscations such as leetspeak (e.g., replacing 'e' with '3') or repeated characters (e.g., 'scaaam')?
  • If the list of banned phrases is dynamically updated by a trust & safety team multiple times per minute, how would you design the cache and synchronization mechanism across chat servers?
  • How would you optimize the search if banned phrases contained arbitrary regular expressions or wildcards rather than static word sequences?