Redis Lists
Redis lists are ordered collections of strings, implemented as linked lists. This means adding elements to the head or tail is extremely fast (O(1)), but accessing elements by index is slower (O(N)). Lists are perfect for queues, activity feeds, and recent item tracking.
Implementation: Redis lists are implemented as doubly-linked lists, not arrays. This makes push/pop operations at both ends instant, but random access slower than arrays.
LPUSH and RPUSH - Adding Elements
Add elements to the left (head) or right (tail) of a list:
// LPUSH - Push to left (head)\n127.0.0.1:6379> LPUSH notifications "New message"\n(integer) 1\n\n127.0.0.1:6379> LPUSH notifications "Friend request"\n(integer) 2\n\n// List now: ["Friend request", "New message"]\n\n// RPUSH - Push to right (tail)\n127.0.0.1:6379> RPUSH tasks "Task 1"\n(integer) 1\n\n127.0.0.1:6379> RPUSH tasks "Task 2" "Task 3"\n(integer) 3\n\n// List now: ["Task 1", "Task 2", "Task 3"]\n\n// Push multiple values at once\n127.0.0.1:6379> LPUSH queue "item1" "item2" "item3"\n(integer) 3
// Laravel examples\n// Add to beginning (most recent first)\nRedis::lpush('user:1000:notifications', 'New comment on your post');\n\n// Add to end (FIFO queue)\nRedis::rpush('email:queue', json_encode([\n 'to' => 'user@example.com',\n 'subject' => 'Welcome!',\n 'body' => 'Thanks for signing up'\n]));
LPOP and RPOP - Removing Elements
Remove and return elements from the left (head) or right (tail):
// LPOP - Pop from left (head)\n127.0.0.1:6379> RPUSH queue "first" "second" "third"\n(integer) 3\n\n127.0.0.1:6379> LPOP queue\n"first"\n\n127.0.0.1:6379> LPOP queue\n"second"\n\n// RPOP - Pop from right (tail)\n127.0.0.1:6379> RPOP queue\n"third"\n\n// Pop from empty list returns nil\n127.0.0.1:6379> LPOP queue\n(nil)\n\n// Pop multiple elements at once (Redis 6.2+)\n127.0.0.1:6379> LPOP queue 3\n1) "item1"\n2) "item2"\n3) "item3"
// Laravel queue processing\nwhile ($job = Redis::lpop('jobs:pending')) {\n $data = json_decode($job, true);\n $this->processJob($data);\n}
Queue Patterns:- FIFO Queue: RPUSH (add) + LPOP (process)
- LIFO Stack: LPUSH (add) + LPOP (process)
- Recent Items: LPUSH (add) + LRANGE 0 9 (get last 10)
LRANGE - Retrieve List Elements
Get a range of elements from a list. Indices are zero-based, and negative indices count from the end:
// Create a list\n127.0.0.1:6379> RPUSH fruits "apple" "banana" "cherry" "date" "elderberry"\n(integer) 5\n\n// LRANGE - Get range of elements\n127.0.0.1:6379> LRANGE fruits 0 2\n1) "apple"\n2) "banana"\n3) "cherry"\n\n// Get all elements (0 to -1)\n127.0.0.1:6379> LRANGE fruits 0 -1\n1) "apple"\n2) "banana"\n3) "cherry"\n4) "date"\n5) "elderberry"\n\n// Get last 2 elements\n127.0.0.1:6379> LRANGE fruits -2 -1\n1) "date"\n2) "elderberry"\n\n// Get first element only\n127.0.0.1:6379> LRANGE fruits 0 0\n1) "apple"
// Laravel - Get recent activity feed (last 20 items)\n$activities = Redis::lrange('user:1000:activity', 0, 19);\n\nforeach ($activities as $activity) {\n $data = json_decode($activity, true);\n echo "{$data['action']} at {$data['timestamp']}<br>";\n}\n\n// Pagination with lists\n$page = 2;\n$perPage = 10;\n$start = ($page - 1) * $perPage;\n$end = $start + $perPage - 1;\n\n$items = Redis::lrange('products:featured', $start, $end);
LLEN - List Length
Get the number of elements in a list:
127.0.0.1:6379> RPUSH mylist "a" "b" "c"\n(integer) 3\n\n127.0.0.1:6379> LLEN mylist\n(integer) 3\n\n// Empty or non-existent list returns 0\n127.0.0.1:6379> LLEN nonexistent\n(integer) 0
// Laravel - Check queue size\n$queueSize = Redis::llen('jobs:pending');\n\nif ($queueSize > 1000) {\n // Alert: queue is growing too large\n Log::warning("Queue size exceeded threshold: {$queueSize}");\n}
List Trimming and Maintenance
Keep lists at a manageable size by trimming old elements:
// LTRIM - Trim list to specified range\n127.0.0.1:6379> RPUSH logs "log1" "log2" "log3" "log4" "log5"\n(integer) 5\n\n// Keep only first 3 elements\n127.0.0.1:6379> LTRIM logs 0 2\nOK\n\n127.0.0.1:6379> LRANGE logs 0 -1\n1) "log1"\n2) "log2"\n3) "log3"
// Laravel - Keep only last 100 notifications\nRedis::lpush('user:1000:notifications', $newNotification);\nRedis::ltrim('user:1000:notifications', 0, 99); // Keep 0-99 (100 items)\n\n// Alternative: Remove after adding\n$maxSize = 50;\nRedis::lpush('activity_feed', $activity);\n\nif (Redis::llen('activity_feed') > $maxSize) {\n Redis::rpop('activity_feed'); // Remove oldest\n}
Redis Sets
Redis sets are unordered collections of unique strings. Sets are perfect for tags, unique visitors tracking, and set operations like unions and intersections.
Uniqueness: Sets automatically handle uniqueness - adding the same element multiple times has no effect. Sets use hash tables internally, providing O(1) add, remove, and membership checking.
SADD and SMEMBERS - Basic Set Operations
// SADD - Add members to set\n127.0.0.1:6379> SADD tags "php" "laravel" "redis"\n(integer) 3\n\n// Adding duplicate has no effect\n127.0.0.1:6379> SADD tags "php"\n(integer) 0 // 0 = no new elements added\n\n// SMEMBERS - Get all members (unordered!)\n127.0.0.1:6379> SMEMBERS tags\n1) "redis"\n2) "php"\n3) "laravel"\n\n// SCARD - Get set size\n127.0.0.1:6379> SCARD tags\n(integer) 3
// Laravel examples\n// Track unique visitors\nRedis::sadd('page:home:visitors:' . date('Y-m-d'), $userId);\n\n// Get unique visitor count\n$uniqueVisitors = Redis::scard('page:home:visitors:' . date('Y-m-d'));\n\n// Add multiple tags to post\nRedis::sadd('post:1000:tags', 'php', 'tutorial', 'beginner');
SISMEMBER - Check Membership
Check if an element exists in a set - extremely fast O(1) operation:
127.0.0.1:6379> SADD premium_users "1000" "2000" "3000"\n(integer) 3\n\n// SISMEMBER - Check if member exists\n127.0.0.1:6379> SISMEMBER premium_users "1000"\n(integer) 1 // 1 = exists\n\n127.0.0.1:6379> SISMEMBER premium_users "9999"\n(integer) 0 // 0 = does not exist
// Laravel - Check if user is premium\nif (Redis::sismember('premium_users', $userId)) {\n // Grant access to premium features\n return view('dashboard.premium');\n}\n\n// Check if IP is blocked\nif (Redis::sismember('blocked_ips', $request->ip())) {\n abort(403, 'Access denied');\n}
SREM - Remove Members
// SREM - Remove members from set\n127.0.0.1:6379> SADD colors "red" "green" "blue" "yellow"\n(integer) 4\n\n127.0.0.1:6379> SREM colors "green"\n(integer) 1 // 1 = member removed\n\n127.0.0.1:6379> SREM colors "green"\n(integer) 0 // 0 = member didn't exist\n\n// Remove multiple members\n127.0.0.1:6379> SREM colors "red" "blue"\n(integer) 2
// Laravel - Remove user from set\nRedis::srem('online_users', $userId);\n\n// Remove expired sessions\n$expiredSessions = Session::where('expires_at', '<', now())->pluck('id');\nRedis::srem('active_sessions', ...$expiredSessions);
SUNION - Set Union
Combine multiple sets and return all unique elements:
// Create sets\n127.0.0.1:6379> SADD skills:user1 "php" "javascript" "mysql"\n(integer) 3\n\n127.0.0.1:6379> SADD skills:user2 "javascript" "python" "redis"\n(integer) 3\n\n// SUNION - Get union of sets\n127.0.0.1:6379> SUNION skills:user1 skills:user2\n1) "php"\n2) "javascript"\n3) "mysql"\n4) "python"\n5) "redis"\n\n// SUNIONSTORE - Store result in new set\n127.0.0.1:6379> SUNIONSTORE all_skills skills:user1 skills:user2\n(integer) 5
// Laravel - Find all users who liked any of these posts\n$postIds = [100, 101, 102];\n$likeKeys = array_map(fn($id) => "post:{$id}:likes", $postIds);\n\n$allLikers = Redis::sunion(...$likeKeys);\necho "Total unique users who liked: " . count($allLikers);
SINTER - Set Intersection
Find common elements across multiple sets:
// SINTER - Get intersection (common elements)\n127.0.0.1:6379> SADD languages:project1 "php" "javascript" "html"\n(integer) 3\n\n127.0.0.1:6379> SADD languages:project2 "javascript" "python" "html"\n(integer) 3\n\n127.0.0.1:6379> SINTER languages:project1 languages:project2\n1) "javascript"\n2) "html"\n\n// SINTERSTORE - Store result\n127.0.0.1:6379> SINTERSTORE common_languages languages:project1 languages:project2\n(integer) 2
// Laravel - Find users in multiple groups\n$group1Members = Redis::smembers('group:admins');\n$group2Members = Redis::smembers('group:moderators');\n\n// Users who are both admins AND moderators\n$superUsers = Redis::sinter('group:admins', 'group:moderators');\n\n// Find products with multiple tags\n$phpProducts = Redis::sinter('tag:php', 'tag:tutorial', 'tag:beginner');
SDIFF - Set Difference
Find elements in one set that are not in other sets:
// SDIFF - Get difference (elements in first but not in others)\n127.0.0.1:6379> SADD set1 "a" "b" "c" "d"\n(integer) 4\n\n127.0.0.1:6379> SADD set2 "b" "d"\n(integer) 2\n\n127.0.0.1:6379> SDIFF set1 set2\n1) "a"\n2) "c"\n\n// SDIFFSTORE - Store result\n127.0.0.1:6379> SDIFFSTORE result set1 set2\n(integer) 2
// Laravel - Find users who haven't completed tutorial\n$allUsers = Redis::smembers('users:registered');\n$completedUsers = Redis::smembers('users:completed_tutorial');\n\n// Users who need to complete tutorial\n$pendingUsers = Redis::sdiff('users:registered', 'users:completed_tutorial');\n\n// Find posts not yet read by user\n$allPosts = Redis::smembers('posts:published');\n$readPosts = Redis::smembers('user:1000:read_posts');\n$unreadPosts = Redis::sdiff('posts:published', 'user:1000:read_posts');
Practical Use Cases
// 1. Tag system with sets\nclass PostTagService {\n public function addTags($postId, array $tags) {\n Redis::sadd("post:{$postId}:tags", ...$tags);\n \n foreach ($tags as $tag) {\n Redis::sadd("tag:{$tag}:posts", $postId);\n }\n }\n \n public function getPostsByTag($tag) {\n return Redis::smembers("tag:{$tag}:posts");\n }\n \n public function getCommonTags($postId1, $postId2) {\n return Redis::sinter("post:{$postId1}:tags", "post:{$postId2}:tags");\n }\n}\n\n// 2. Activity feed with lists\nclass ActivityFeed {\n public function addActivity($userId, $activity) {\n $data = json_encode([\n 'action' => $activity,\n 'timestamp' => now()->toIso8601String()\n ]);\n \n Redis::lpush("user:{$userId}:feed", $data);\n Redis::ltrim("user:{$userId}:feed", 0, 49); // Keep last 50\n }\n \n public function getFeed($userId, $limit = 20) {\n $feed = Redis::lrange("user:{$userId}:feed", 0, $limit - 1);\n return array_map(fn($item) => json_decode($item, true), $feed);\n }\n}\n\n// 3. Online users tracking with sets\nclass OnlineUsers {\n public function markOnline($userId) {\n Redis::sadd('users:online', $userId);\n Redis::setex("user:{$userId}:last_seen", 300, time());\n }\n \n public function getOnlineCount() {\n return Redis::scard('users:online');\n }\n \n public function isOnline($userId) {\n return Redis::sismember('users:online', $userId);\n }\n}
Performance Considerations:- Lists: O(N) for LRANGE - don't get huge ranges
- Sets: SMEMBERS is O(N) - avoid on very large sets (>10K members)
- Set operations (SUNION, SINTER) can be slow on large sets
- Use SSCAN instead of SMEMBERS for large sets
Exercise: Build a social network feature system:
- Implement "following" using sets (user:X:following, user:X:followers)
- Create a function to follow/unfollow users
- Implement mutual friends (users who follow each other)
- Build an activity feed using lists that stores last 50 activities
- Create a tagging system where posts can have multiple tags
- Implement "suggested friends" (followers of your followers)
In the next lesson, we'll explore Redis Hashes and Sorted Sets - advanced data structures for more complex use cases.