· 6 min read

Making Telegram Moderation Faster with Event-Driven Filters and O(1) Lookups

If your Telegram moderation bot starts lagging under load, the bottleneck is usually not Telegram itself but the way your moderation pipeline inspects every message. This tutorial shows how to restructure moderation around event-driven filters, cache-friendly state, and constant-time lookups so you can keep response times low as your group grows.

Moderation bots often start simple: check every message against a list of banned words, inspect the sender’s status, maybe hit a database, then decide whether to delete, warn, mute, or ignore. That works fine in a small group. In a busy Telegram supergroup, it becomes expensive fast.

The performance problem is usually not one dramatic slow call. It is the accumulation of small costs:

  • scanning long lists of rules on every message
  • repeated database reads for user state
  • expensive regex checks for messages that could have been rejected earlier
  • duplicate work across handlers that each re-evaluate the same message

The fix is to make moderation event-driven, stateful, and cheap to evaluate.

This tutorial walks through a practical architecture for Telegram moderation that keeps per-message overhead low.

The core idea

Instead of treating every incoming update as a fresh decision from scratch, split moderation into two phases:

  1. Precompute state when rules change, not when messages arrive.
  2. Evaluate messages with constant-time checks whenever possible.

That means:

  • store moderation rules in compiled or indexed form
  • cache user and chat state in memory with a TTL or invalidation strategy
  • short-circuit the most common cases first
  • only fall back to heavy checks when necessary

In a high-traffic group, this can reduce moderation latency dramatically.

What usually slows Telegram moderation down

Before optimizing, identify the common bottlenecks.

1. Linear scans over rules

A typical implementation keeps banned terms in a list and checks each message against the whole list:

for term in banned_terms:
    if term in message_text:
        return True

That is fine for a dozen rules. It is not fine for hundreds or thousands.

2. Repeated database calls

If every message triggers queries like:

  • is this user muted?
  • is flood control active?
  • what language filters apply to this chat?

then your moderation path becomes I/O bound.

3. Regex used too early

Regex is powerful, but expensive relative to simple string operations. If you run regex before checking obvious conditions such as empty text, command messages, or trusted users, you waste CPU.

4. Multiple handlers repeating the same work

In bot frameworks, different handlers sometimes each inspect the same message independently. For example, one handler checks spam, another checks links, and another checks mentions. If they each parse the message and load state separately, your cost multiplies.

Build a moderation pipeline with fast exits

The fastest moderation system is the one that rejects most work before it becomes expensive.

A good evaluation order is:

  1. skip non-text or safe update types when rules do not apply
  2. ignore trusted users and exempt chats early
  3. check cheap exact-match rules
  4. check precomputed hash/set membership
  5. check cached user/chat moderation state
  6. only then run regex, pattern matching, or external lookups

That structure matters more than micro-optimizing a single function.

Use sets and maps, not lists

If you need to test membership often, store the data in a set or dictionary.

Example: banned usernames

banned_usernames = {"badactor1", "spamaccount2", "phishbot3"}

if username and username.lower() in banned_usernames:
    flag_user(message)

A set gives you average O(1) membership checks.

Example: chat rules by type

chat_rules = {
    "links": True,
    "stickers": False,
    "caps_ratio": 0.7,
}

A dictionary lets you retrieve rule values immediately instead of iterating through a list of rule objects.

Precompile expensive patterns

If you use regex, compile patterns once at startup or when rules change.

import re

LINK_RE = re.compile(r"https?://|t\.me/|telegram\.me/", re.IGNORECASE)
MENTION_RE = re.compile(r"@\w+")

Then use them repeatedly:

if LINK_RE.search(text):
    delete_message(message)

Do not rebuild the regex object for every update.

Better: gate regex behind cheaper checks

text = message.text

if not text:
    return

if "http" not in text and "t.me" not in text and "@" not in text:
    return

if LINK_RE.search(text):
    delete_message(message)

This avoids regex entirely for most messages.

Cache user and chat moderation state

A lot of moderation logic depends on state that changes infrequently:

  • muted until a timestamp
  • shadowbanned
  • flood score
  • exemption role
  • chat-specific rule configuration

Loading this from storage on every message is a common performance killer.

Use an in-memory cache

Keep a local cache keyed by chat ID and user ID:

user_state_cache = {}
chat_rule_cache = {}

Then read from cache first:

state = user_state_cache.get((chat_id, user_id))
if state is None:
    state = load_user_state(chat_id, user_id)
    user_state_cache[(chat_id, user_id)] = state

Add TTL or invalidation

Caching only works if stale data does not break moderation.

Use one of these strategies:

  • TTL cache: expire entries after a short period
  • event invalidation: clear cache when a moderation action changes state
  • versioned config: reload chat rules when a revision number changes

For moderation, a short TTL plus explicit invalidation is usually enough.

Separate read-heavy checks from write-heavy actions

Deletion, bans, restrictions, and audit logging are actions. They do not belong in the same fast path as the decision logic if you can avoid it.

A clean pattern is:

  1. evaluate the message
  2. emit a moderation event
  3. process actions asynchronously

For example:

result = evaluate_message(message)
if result.should_flag:
    moderation_queue.put(result)

A background worker can then handle:

  • deleting the message
  • warning the user
  • writing to logs
  • notifying admins

This keeps your update handler responsive.

Short-circuit by trust level

One of the easiest wins is to skip expensive moderation for users who do not need it.

Examples:

  • admins
  • owners
  • approved users
  • users with a probation period already cleared
if is_trusted_user(chat_id, user_id):
    return

Place this as early as possible. If trusted users are common, you save a lot of work.

Avoid re-parsing message content

Some bots parse the same message multiple times: once for links, once for mentions, once for command detection, and once for spam heuristics.

Instead, normalize once and reuse the result.

text = (message.text or "").strip()
lower_text = text.lower()
contains_url = "http" in lower_text or "t.me" in lower_text
contains_mention = "@" in text

Then pass the normalized data to your checks.

Make moderation rules data-driven

Hard-coded if/else chains are difficult to optimize and difficult to maintain.

Prefer a rule model like this:

@dataclass
class ModerationRule:
    kind: str
    enabled: bool
    value: Any
    action: str

Then pre-index the rules by kind:

rules_by_kind = {
    "links": link_rules,
    "caps": caps_rules,
    "keywords": keyword_rules,
}

When a message arrives, only inspect the relevant rule bucket. This avoids scanning unrelated rules.

A practical evaluation pattern

Here is a compact example of a fast moderation flow.

def moderate_message(message, cache, compiled_patterns):
    chat_id = message.chat.id
    user_id = message.from_user.id

    # 1. Fast trust check
    if cache.is_trusted(chat_id, user_id):
        return None

    # 2. Skip irrelevant updates
    text = message.text or message.caption or ""
    if not text:
        return None

    # 3. Load chat config from cache
    config = cache.get_chat_config(chat_id)
    if not config.enabled:
        return None

    # 4. Cheap checks first
    lower = text.lower()
    if config.block_links and ("http" in lower or "t.me" in lower):
        return {"action": "delete", "reason": "link"}

    # 5. Set membership checks
    if lower in config.banned_phrases:
        return {"action": "delete", "reason": "phrase"}

    # 6. Regex only if needed
    if config.block_patterns:
        for pattern in compiled_patterns[chat_id]:
            if pattern.search(text):
                return {"action": "warn", "reason": "pattern"}

    return None

Notice what this avoids:

  • no database read on every update
  • no repeated config loading
  • no regex unless earlier checks fail to catch the issue
  • no evaluation for trusted users

Use batching for logs and analytics

Logging every moderation decision synchronously can become a hidden bottleneck.

If you send each event to a database, file, or remote analytics service in the request path, your bot will slow down.

Instead:

  • push moderation events to a queue
  • batch writes every few seconds
  • flush immediately only for critical events

A simple batching approach can improve throughput a lot while keeping logs complete enough for review.

Measure the right things

Optimization without measurement is guesswork.

Track:

  • average moderation decision time
  • p95 and p99 latency
  • cache hit rate
  • number of database calls per message
  • number of regex evaluations per message
  • number of messages short-circuited by trust checks

If your cache hit rate is low, improve cache lifetime or invalidation. If regex evaluations are high, move more checks ahead of them. If database calls remain frequent, remove the dependency from the hot path.

Common mistakes to avoid

Over-caching mutable state

If you cache user status too long, a muted user may continue speaking after being unmuted, or vice versa. Keep expiry tight and invalidate on admin actions.

Optimizing the wrong layer

Do not spend time shaving microseconds off string operations if the real cost is a network round trip to your database.

Letting every handler do its own work

Centralize normalization and state lookup so the same message is not parsed multiple times.

Running expensive checks globally

Apply heavy filters only to chats or users that actually need them.

A good target architecture

For a production Telegram moderation bot, aim for this structure:

  • ingest layer: receive updates and normalize content once
  • decision layer: evaluate with cached state and indexed rules
  • action layer: delete, warn, mute, or log asynchronously
  • configuration layer: update caches when rules or permissions change

This separation keeps the hot path small.

Final takeaway

The biggest moderation performance gains usually come from design, not syntax. If your bot is slowing down, stop scanning long lists, stop querying storage on every message, and stop running expensive checks before cheap ones.

Use sets, cached state, precompiled patterns, and short-circuit logic. Make moderation event-driven, and reserve heavy work for the small fraction of messages that truly need it.

That is how you keep a Telegram moderation bot fast as traffic scales.

← All posts