User Blocking System — Low-Level Design
A user blocking system prevents one user from seeing or interacting with another. It must enforce blocks across all content surfaces, handle bidirectional visibility, and scale to millions of block relationships. This design is asked at Twitter, Instagram, and any social platform.
Core Data Model
Block
blocker_id BIGINT NOT NULL -- user who initiated the block
blocked_id BIGINT NOT NULL -- user who was blocked
created_at TIMESTAMPTZ NOT NULL
PRIMARY KEY (blocker_id, blocked_id)
-- Index for the reverse lookup: "who has blocked me?"
CREATE INDEX idx_block_blocked ON Block(blocked_id, blocker_id);
-- Checking both directions:
-- A blocks B: (blocker=A, blocked=B)
-- Neither A sees B's content NOR B sees A's content
-- "Has a block relationship between A and B?"
-- SELECT 1 FROM Block WHERE (blocker_id=A AND blocked_id=B)
-- OR (blocker_id=B AND blocked_id=A)
Blocking and Unblocking
def block_user(blocker_id, blocked_id):
if blocker_id == blocked_id:
raise ValidationError('Cannot block yourself')
db.execute("""
INSERT INTO Block (blocker_id, blocked_id, created_at)
VALUES (%(blocker)s, %(blocked)s, NOW())
ON CONFLICT DO NOTHING
""", {'blocker': blocker_id, 'blocked': blocked_id})
# Side effects:
# 1. Remove follow relationships in both directions
db.execute("""
DELETE FROM Follow
WHERE (follower_id=%(a)s AND followee_id=%(b)s)
OR (follower_id=%(b)s AND followee_id=%(a)s)
""", {'a': blocker_id, 'b': blocked_id})
# 2. Remove from each other's follower counts (denormalized)
# (handled by the DELETE trigger or application code)
# 3. Invalidate block check cache
cache_key = f'block:{min(blocker_id, blocked_id)}:{max(blocker_id, blocked_id)}'
redis.delete(cache_key)
def unblock_user(blocker_id, blocked_id):
db.execute("""
DELETE FROM Block WHERE blocker_id=%(blocker)s AND blocked_id=%(blocked)s
""", {'blocker': blocker_id, 'blocked': blocked_id})
cache_key = f'block:{min(blocker_id, blocked_id)}:{max(blocker_id, blocked_id)}'
redis.delete(cache_key)
Block Check at Read Time
def are_blocked(user_a, user_b):
"""Returns True if either user has blocked the other."""
cache_key = f'block:{min(user_a, user_b)}:{max(user_a, user_b)}'
cached = redis.get(cache_key)
if cached is not None:
return cached == b'1'
result = db.execute("""
SELECT 1 FROM Block
WHERE (blocker_id=%(a)s AND blocked_id=%(b)s)
OR (blocker_id=%(b)s AND blocked_id=%(a)s)
LIMIT 1
""", {'a': user_a, 'b': user_b}).first()
blocked = result is not None
redis.setex(cache_key, 300, '1' if blocked else '0')
return blocked
Filtering Blocked Users from Feed Queries
-- Option 1: JOIN-based filter (good for small block lists)
SELECT p.* FROM Post p
WHERE p.author_id NOT IN (
SELECT blocked_id FROM Block WHERE blocker_id=%(viewer)s
UNION
SELECT blocker_id FROM Block WHERE blocked_id=%(viewer)s
)
AND p.author_id = %(target)s;
-- Option 2: Pre-loaded block list (good for feed generation)
def get_feed(viewer_id):
# Load viewer's block list once (usually small, < 1000 entries)
blocked_users = get_block_list(viewer_id) # cached in Redis
# Filter at application level
posts = get_candidate_posts(viewer_id)
return [p for p in posts if p.author_id not in blocked_users]
def get_block_list(user_id):
"""All user IDs in a block relationship with this user."""
key = f'block_list:{user_id}'
cached = redis.get(key)
if cached:
return set(json.loads(cached))
rows = db.execute("""
SELECT blocked_id AS other_id FROM Block WHERE blocker_id=%(uid)s
UNION
SELECT blocker_id AS other_id FROM Block WHERE blocked_id=%(uid)s
""", {'uid': user_id})
ids = {r.other_id for r in rows}
redis.setex(key, 300, json.dumps(list(ids)))
return ids
Blocking Side Effects Checklist
When A blocks B, handle all of these:
[x] Remove A→B follow
[x] Remove B→A follow
[x] Hide A's content from B's feed
[x] Hide B's content from A's feed
[x] B cannot reply to A's posts
[x] B cannot DM A
[x] A's profile is not visible to B (404 or "User not found")
[x] B's @mentions of A are hidden from A's notifications
[x] A does not appear in B's search results
[x] Shared group chats: A and B can still see messages (platform-dependent)
Most platforms: blocking doesn't affect shared channels/groups
Key Interview Points
- Bidirectional semantics: A block is one-directional in the DB (blocker → blocked) but has bidirectional visibility effects. When checking “can X see Y’s content?” always check both (X→Y) and (Y→X) in the Block table.
- Cache the block list, not individual pairs: For feed filtering, loading the full block list once (usually <100 entries) and filtering at the application level is faster than per-post SQL subqueries.
- Invalidate caches on block/unblock: The block relationship cache must be invalidated immediately on change. A stale cache that shows a blocked user’s content violates the user’s expectations.
- Remove follows on block: A block should silently remove mutual follows so that unblocking later doesn’t automatically restore the follow relationship without user consent.
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”How do you enforce blocking in feed and search queries efficiently?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Loading the full block list once per request (usually <100 entries) and filtering at the application level is faster than adding NOT IN subqueries to every query. Cache the block list in Redis: SET block_list:{user_id} [ids] EX 300. On feed generation: load the block list once, filter out blocked user_ids from the candidate set before rendering. For search: add a terms filter in Elasticsearch excluding blocked user IDs. For very active users with large block lists (>1000), consider a Bloom filter for probabilistic O(1) membership testing.”}},{“@type”:”Question”,”name”:”What content surfaces must enforce blocking?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”A comprehensive block must apply to: posts and content appearing in feeds, search results, @mention suggestions, comment sections (blocked users cannot reply), DMs (blocked user cannot initiate a conversation), follower/following lists (block removes the follow), notification delivery (no notifications from blocked users), profile visibility (blocked user sees "user not found"), suggested users / "people you may know" recommendations. Missing any surface creates a harassment vector. Enumerate all surfaces in a checklist and test each during implementation.”}},{“@type”:”Question”,”name”:”How do you handle a block within shared group contexts?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Most platforms treat group channels differently from direct interactions: blocking does not remove two users from a shared group chat or a shared event. The rationale: the group existed before the block; forcing one party to leave violates their membership. The standard behavior: in a shared group, messages from the blocked user are hidden (collapsed) for the blocker with an option to reveal. The blocked user cannot see whether they are blocked in that context — they still see their messages sent. Document this edge case explicitly; it surprises interviewers who assume blocking is total.”}},{“@type”:”Question”,”name”:”How do you remove follow relationships when a block is created?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”In the same transaction as inserting the Block row: DELETE FROM Follow WHERE (follower_id=blocker AND followee_id=blocked) OR (follower_id=blocked AND followee_id=blocker). This is critical: without removing follows, the blocked user’s posts still appear in the blocker’s feed (via the follow relationship), even though the block should hide them. Additionally, decrement the follower_count and following_count denormalized columns for both users. Use a database transaction so the Block insert and Follow delete are atomic — no partial state where the block exists but the follow does not yet.”}},{“@type”:”Question”,”name”:”How do you prevent a blocked user from knowing they are blocked?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Return the same response whether the user is blocked or the target user does not exist: 404 Not Found with "User not found." Never return 403 Forbidden or "You are blocked" — that confirms the target account exists and that a block relationship exists. This also prevents enumeration: an attacker cannot test whether they are blocked by specific users by checking response codes. Apply the same principle to search: simply omit blocked users from results rather than returning them with a blocked indicator.”}}]}
User blocking and harassment prevention design is discussed in Twitter system design interview questions.
User blocking and content visibility design is covered in Meta system design interview preparation.
User blocking and friend system design is discussed in Snap system design interview guide.