What are Sets?
A Set is an unordered collection of unique items. No duplicates allowed. Perfect for tracking memberships, relationships, and finding common elements between groups.
Think of it as a collection of tags or group memberships that you can quickly check.
Basic Commands
SADD tags "javascript" "web" "programming"
SADD tags "javascript" # Already there, ignored
SMEMBERS tags # Get all items
# Returns: {"javascript", "web", "programming"}
SCARD tags # Size: 3
SISMEMBER tags "web" # Is "web" in the set? Yes (true)
SREM tags "web" # Remove "web"
SPOP tags # Remove and return a random item
Tracking Unique Users
Count unique visitors without duplicates:
# Each day, track who visited
SADD visitors:2026-02-09 "user:1" "user:2" "user:3" "user:1"
SADD visitors:2026-02-09 "user:2" # Already there, ignored
# How many unique visitors?
SCARD visitors:2026-02-09 # 3 (user:1 counted once)
Node.js (ioredis):
import Redis from 'ioredis';
const redis = new Redis();
// Track page visitor
async function trackVisitor(pageId, userId) {
const today = new Date().toISOString().split('T')[0];
const key = `page:${pageId}:visitors:${today}`;
// SADD returns 1 if new, 0 if already exists
const isNew = await redis.sadd(key, userId);
await redis.expire(key, 86400 * 7); // Keep for 7 days
return { isNewVisitor: isNew === 1 };
}
// Get unique visitor count
async function getUniqueVisitors(pageId, date) {
const key = `page:${pageId}:visitors:${date}`;
return await redis.scard(key);
}
// Check if user visited before
async function hasVisited(pageId, userId, date) {
const key = `page:${pageId}:visitors:${date}`;
return await redis.sismember(key, userId) === 1;
}
Python (redis-py):
import redis
from datetime import date
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
def track_visitor(page_id: int, user_id: str) -> dict:
today = date.today().isoformat()
key = f"page:{page_id}:visitors:{today}"
# SADD returns 1 if new, 0 if already exists
is_new = r.sadd(key, user_id)
r.expire(key, 86400 * 7) # Keep for 7 days
return {'is_new_visitor': is_new == 1}
def get_unique_visitors(page_id: int, day: str = None) -> int:
day = day or date.today().isoformat()
key = f"page:{page_id}:visitors:{day}"
return r.scard(key)
# Usage
track_visitor(1, "user:123")
count = get_unique_visitors(1)
Finding Common Items (SINTER)
Who are friends of both Alice and Bob?
SADD alice:friends "bob" "charlie" "diana" "eve"
SADD bob:friends "alice" "charlie" "frank" "grace"
# Find mutual friends
SINTER alice:friends bob:friends
# Result: {"charlie"}
# Get list and store it
SINTERSTORE mutual_friends alice:friends bob:friends
# Now mutual_friends = {"charlie"}
Node.js (ioredis):
// Find mutual friends
async function getMutualFriends(userId1, userId2) {
return await redis.sinter(
`user:${userId1}:friends`,
`user:${userId2}:friends`
);
}
// Find common interests for recommendations
async function getCommonInterests(userId1, userId2) {
const common = await redis.sinter(
`user:${userId1}:interests`,
`user:${userId2}:interests`
);
return common;
}
// Friend suggestions based on mutual connections
async function getFriendSuggestions(userId) {
const friends = await redis.smembers(`user:${userId}:friends`);
const suggestions = new Set();
for (const friendId of friends) {
// Get friends of friends
const fof = await redis.sdiff(
`user:${friendId}:friends`,
`user:${userId}:friends`
);
fof.forEach(f => f !== userId && suggestions.add(f));
}
return Array.from(suggestions).slice(0, 10);
}
Python (redis-py):
def get_mutual_friends(user_id: str, other_id: str) -> list:
return list(r.sinter(
f"user:{user_id}:friends",
f"user:{other_id}:friends"
))
def get_common_interests(user_id: str, other_id: str) -> list:
return list(r.sinter(
f"user:{user_id}:interests",
f"user:{other_id}:interests"
))
def get_friend_suggestions(user_id: str, limit: int = 10) -> list:
friends = r.smembers(f"user:{user_id}:friends")
suggestions = set()
for friend_id in friends:
# Friends of friends, excluding current friends
fof = r.sdiff(
f"user:{friend_id}:friends",
f"user:{user_id}:friends"
)
suggestions.update(f for f in fof if f != user_id)
return list(suggestions)[:limit]
# Usage
mutual = get_mutual_friends("alice", "bob")
common = get_common_interests("user:1", "user:2")
suggestions = get_friend_suggestions("user:1")
Union - All Items from Multiple Sets (SUNION)
Combine multiple groups:
SADD alice:friends "bob" "charlie" "diana"
SADD bob:friends "charlie" "frank"
SADD charlie:friends "alice" "diana" "eve"
# All their friends (combined)
SUNION alice:friends bob:friends charlie:friends
# Result: {"bob", "charlie", "diana", "frank", "alice", "eve"}
Real-world use:
# Notify all users who follow these topics
SADD followers:javascript "user:1" "user:2" "user:5"
SADD followers:nodejs "user:2" "user:3" "user:5"
# New JavaScript article posted, notify everyone
SUNION followers:javascript followers:nodejs
# Get all unique followers
Difference - Items Only in One Set (SDIFF)
Find items that are in one set but not in others:
SADD alice:friends "bob" "charlie" "diana" "eve"
SADD bob:friends "alice" "charlie" "frank"
# Who are Alice's friends that Bob isn't friends with?
SDIFF alice:friends bob:friends
# Result: {"diana", "eve"}
Real-world use:
# Find features this user hasn't explored yet
SADD all:features "profile" "settings" "notifications" "analytics" "billing"
SADD user:1:explored "profile" "settings"
# What's new for them to discover?
SDIFF all:features user:1:explored
# Result: {"notifications", "analytics", "billing"}
Tagging System
Use Sets for flexible tagging:
# Add tags to articles
SADD article:1:tags "redis" "caching" "performance"
SADD article:2:tags "redis" "streams" "messaging"
SADD article:3:tags "caching" "database"
# Articles with "redis" tag
SADD tag:redis:articles "article:1" "article:2"
# Find articles with BOTH "redis" AND "caching"
SINTER tag:redis:articles tag:caching:articles
# Result: {"article:1"}
# OR - find articles with EITHER tag
SUNION tag:redis:articles tag:caching:articles
# Result: {"article:1", "article:2", "article:3"}
Node.js (ioredis):
// Add tags to an article
async function tagArticle(articleId, tags) {
const pipeline = redis.pipeline();
// Add tags to article's tag set
pipeline.sadd(`article:${articleId}:tags`, ...tags);
// Add article to each tag's article set
for (const tag of tags) {
pipeline.sadd(`tag:${tag}:articles`, `article:${articleId}`);
}
await pipeline.exec();
}
// Find articles by multiple tags (AND)
async function findArticlesByAllTags(tags) {
const keys = tags.map(t => `tag:${t}:articles`);
return await redis.sinter(...keys);
}
// Find articles by any tag (OR)
async function findArticlesByAnyTag(tags) {
const keys = tags.map(t => `tag:${t}:articles`);
return await redis.sunion(...keys);
}
// Usage
await tagArticle(1, ['redis', 'caching', 'performance']);
const articles = await findArticlesByAllTags(['redis', 'caching']);
Python (redis-py):
def tag_article(article_id: int, tags: list[str]):
pipe = r.pipeline()
# Add tags to article's tag set
pipe.sadd(f"article:{article_id}:tags", *tags)
# Add article to each tag's article set
for tag in tags:
pipe.sadd(f"tag:{tag}:articles", f"article:{article_id}")
pipe.execute()
def find_articles_by_tags(tags: list[str], match_all: bool = True) -> list:
keys = [f"tag:{t}:articles" for t in tags]
if match_all: # AND - must have all tags
return list(r.sinter(*keys))
else: # OR - any of the tags
return list(r.sunion(*keys))
# Usage
tag_article(1, ['redis', 'caching', 'performance'])
articles = find_articles_by_tags(['redis', 'caching'], match_all=True)
Blocking Social Network
Block users and check quickly:
# User blocks these people
SADD user:1:blocked "user:2" "user:5" "user:10"
# Can they message each other?
if SISMEMBER user:1:blocked "user:2"
REJECT("User blocked")
Deduplication
Remove duplicates from a list:
# Get all items and remove duplicates
SADD unique_items "apple" "banana" "apple" "orange" "banana"
SMEMBERS unique_items
# Result: {"apple", "banana", "orange"}
Permissions and Roles
Track what a user can do:
# User 1 has these permissions
SADD user:1:permissions "read" "write" "delete" "admin"
# Check permission
if SISMEMBER user:1:permissions "admin"
allow_admin_panel()
Node.js (ioredis):
// Check single permission
async function hasPermission(userId, permission) {
return await redis.sismember(`user:${userId}:permissions`, permission) === 1;
}
// Check multiple permissions (all required)
async function hasAllPermissions(userId, permissions) {
const userPerms = `user:${userId}:permissions`;
for (const perm of permissions) {
if (await redis.sismember(userPerms, perm) !== 1) {
return false;
}
}
return true;
}
// Middleware for Express/Fastify
function requirePermission(...permissions) {
return async (req, res, next) => {
const userId = req.user.id;
const hasAll = await hasAllPermissions(userId, permissions);
if (!hasAll) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// Usage
app.delete('/users/:id', requirePermission('admin', 'delete'), deleteUser);
Python (redis-py):
def has_permission(user_id: str, permission: str) -> bool:
return r.sismember(f"user:{user_id}:permissions", permission) == 1
def has_all_permissions(user_id: str, permissions: list[str]) -> bool:
user_perms = f"user:{user_id}:permissions"
return all(r.sismember(user_perms, p) for p in permissions)
def require_permissions(user_id: str, permissions: list[str]):
"""Raises exception if user lacks required permissions"""
if not has_all_permissions(user_id, permissions):
raise PermissionError(f"User {user_id} lacks required permissions")
# Usage
if has_permission("user:1", "admin"):
print("User is admin")
# Check multiple permissions
if has_all_permissions("user:1", ["admin", "delete"]):
delete_user(target_id)
Sampling
Pick random items:
SADD users "user:1" "user:2" "user:3" "user:4" "user:5"
# Pick a random user
SPOP users
# Returns one random user, removes it
# Pick 3 random users without removing
SRANDMEMBER users 3
# Returns 3 random users
Real-world use:
# Random A/B test selection
SADD experiment:control "user:1" "user:2" "user:3"
SRANDMEMBER experiment:control 1 # Pick random for control
Common Patterns
Follow/Follower System
# Alice follows these people
SADD alice:following "bob" "charlie" "diana"
# Bob's followers
SADD bob:followers "alice" "eve" "frank"
# Check if Alice follows Bob
SISMEMBER alice:following "bob" # Yes
Node.js (ioredis):
// Follow a user
async function followUser(followerId, followeeId) {
const pipeline = redis.pipeline();
pipeline.sadd(`user:${followerId}:following`, followeeId);
pipeline.sadd(`user:${followeeId}:followers`, followerId);
await pipeline.exec();
return { success: true };
}
// Unfollow a user
async function unfollowUser(followerId, followeeId) {
const pipeline = redis.pipeline();
pipeline.srem(`user:${followerId}:following`, followeeId);
pipeline.srem(`user:${followeeId}:followers`, followerId);
await pipeline.exec();
return { success: true };
}
// Check if following
async function isFollowing(followerId, followeeId) {
return await redis.sismember(`user:${followerId}:following`, followeeId) === 1;
}
// Get follower/following counts
async function getFollowStats(userId) {
const [followers, following] = await Promise.all([
redis.scard(`user:${userId}:followers`),
redis.scard(`user:${userId}:following`)
]);
return { followers, following };
}
Python (redis-py):
def follow_user(follower_id: str, followee_id: str):
pipe = r.pipeline()
pipe.sadd(f"user:{follower_id}:following", followee_id)
pipe.sadd(f"user:{followee_id}:followers", follower_id)
pipe.execute()
def unfollow_user(follower_id: str, followee_id: str):
pipe = r.pipeline()
pipe.srem(f"user:{follower_id}:following", followee_id)
pipe.srem(f"user:{followee_id}:followers", follower_id)
pipe.execute()
def is_following(follower_id: str, followee_id: str) -> bool:
return r.sismember(f"user:{follower_id}:following", followee_id) == 1
def get_follow_stats(user_id: str) -> dict:
return {
'followers': r.scard(f"user:{user_id}:followers"),
'following': r.scard(f"user:{user_id}:following")
}
# Usage
follow_user("alice", "bob")
print(is_following("alice", "bob")) # True
print(get_follow_stats("bob")) # {'followers': 1, 'following': 0}
Feature Flag Groups
# Which users get the new feature?
SADD feature:new_ui:beta_users "user:1" "user:5" "user:10"
# Is this user in beta?
if SISMEMBER feature:new_ui:beta_users user_id
show_new_ui()
Languages/Skills
# What languages does each developer know?
SADD dev:alice:languages "javascript" "python" "go"
SADD dev:bob:languages "javascript" "ruby" "java"
# Find developers who know Go
SADD language:go:developers "dev:alice" "dev:charlie"