· 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:
- Precompute state when rules change, not when messages arrive.
- 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:
- skip non-text or safe update types when rules do not apply
- ignore trusted users and exempt chats early
- check cheap exact-match rules
- check precomputed hash/set membership
- check cached user/chat moderation state
- 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:
- evaluate the message
- emit a moderation event
- 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.