How Redis Implements Memory-Efficient Sets with Intset

Arpit Bhayani

Arpit Bhayani

Jun 14, 2026 • 8 min read

Play

Note: This article is an AI-generated write-up based on the captions and transcript of the video above. Watch the embedded video for the full visual walk-through and nuances.

How Redis Implements Memory-Efficient Sets with Intset

Redis is renowned for its high performance and efficient memory usage. One of the ways it achieves this is through specialized data structure implementations, particularly for sets. While sets are generally implemented using hash tables, Redis introduces a highly optimized structure called Intset for specific scenarios.

Redis Set Implementations: Hash Table vs. Intset

When you interact with Redis sets using commands like SADD, Redis intelligently chooses the underlying data structure based on the type of elements being stored.

  1. Hash Table (Generic Sets):

    • By default, if you add a string or a mix of data types to a set, Redis uses a hash table for its implementation. This provides robust and flexible storage for diverse elements.
    • You can observe this by running DEBUG OBJECT <key> after adding a string element, which will show encoding: hashtable.
  2. Intset (Integer-Only Sets):

    • If a set only contains integer values, Redis employs a specialized data structure called Intset. This is a highly optimized, memory-efficient implementation designed specifically for integers.
    • If you add only integers to a set and then run DEBUG OBJECT <key>, you will see encoding: intset.

Dynamic Encoding Changes

Redis is flexible. It can dynamically change the encoding of a set:

  • Intset to Hash Table: If a set initially uses Intset (because all elements were integers) but then a non-integer value (e.g., a string) is added, Redis automatically converts the Intset to a hash table. This involves reallocating memory and migrating all existing integer elements to the new hash table structure, which can be an expensive operation.
  • Initial Type Determination: The type of the first element added to a set often determines its initial encoding. If the first element can be represented as a long long integer, Redis will likely start with an Intset.

This dynamic adaptation ensures that Redis always uses the most memory-efficient and performant data structure for the current set of elements.

Deep Dive into Intset

Intset is a fascinating example of Redis’s memory frugality. It’s designed to store unique integers in a highly compact manner.

What is Intset?

An Intset is essentially a sorted list (or array) of unique integers. Since sets inherently do not allow duplicates, maintaining a sorted list of integers naturally fulfills the requirements of a set while offering significant memory and performance advantages for integer-specific operations.

Intset Memory Layout

The Intset structure in memory is a contiguous block, similar to an array, with a specific layout:

+----------+--------+-----------+-----------+-----+-----------+
| Encoding | Length | Integer 1 | Integer 2 | ... | Integer N |
+----------+--------+-----------+-----------+-----+-----------+
  • Encoding: This field specifies the bit width used for storing each integer element. Redis doesn’t blindly allocate 64 bits for every integer. Instead, it dynamically chooses the smallest possible encoding:
    • 16-bit integers
    • 32-bit integers
    • 64-bit integers This ensures that only the necessary amount of memory is allocated for each integer, saving significant space, especially when dealing with smaller numbers.
  • Length: This indicates the total number of elements currently stored in the Intset.
  • Integers: This is the actual sorted list of integer elements. Because each integer has a fixed width (determined by Encoding), Redis can directly calculate the memory offset to access any element (e.g., to access the 4th integer, if each is 16-bit, it jumps 3 * 16 bits from the start of the integer data).

Dynamic Encoding Upgrade

Intset is not static in its encoding. If you initially add small integers (e.g., 1, 2, 3) that fit into 16-bit encoding, the Intset will be configured for 16-bit. However, if you later try to insert a much larger integer (e.g., 4 billion) that requires 32-bit or 64-bit storage:

  1. Redis detects that the new value cannot fit into the current encoding.
  2. It upgrades the Intset’s encoding to the next appropriate size (e.g., from 16-bit to 32-bit).
  3. This involves reallocating a larger block of memory.
  4. All existing elements are reloaded into the new, wider format.
  5. Finally, the new, larger integer is inserted.

This is an expensive operation due to reallocation and data migration. However, it’s a trade-off that Redis makes because, in most common use cases, users tend to insert homogeneous types, and the space savings achieved by using the smallest possible encoding are substantial.

Operational Complexity

Given its sorted array structure, Intset offers specific performance characteristics:

  • Search (Does element exist?): O(log N)
    • Since the elements are sorted, Redis can perform a binary search to quickly determine if an element exists or to find its potential insertion point. This is highly efficient for lookup operations.
  • Insertion (Add element): O(N)
    • To maintain the sorted order, inserting a new element in the middle of the array requires shifting existing elements to the right to make space. This is typically done using memmove.
    • In the worst case (inserting at the beginning), all N elements need to be shifted.
  • Deletion (Remove element): O(N)
    • Similarly, deleting an element requires shifting subsequent elements to the left to fill the gap, also using memmove.

Configuration Limit: set-max-intset-entries

While Intset is efficient for smaller sets, a very long sorted array can become inefficient for O(N) insertion/deletion operations. Redis provides a configuration parameter, set-max-intset-entries (defaulting to 512 in recent versions), which defines the maximum number of entries an Intset can hold.

  • If the number of elements in an Intset exceeds this limit, Redis automatically converts the Intset to a hash table. This prevents the performance degradation that would occur with very large sorted arrays for write operations.

Intset Operations in Detail

Let’s look at the mechanics of adding and deleting elements in an Intset.

Adding an Element

  1. Check for Encoding Compatibility: Determine if the new value can fit into the current Intset encoding. If not, trigger an encoding upgrade (reallocate, reload, then proceed).
  2. Binary Search: Perform a binary search on the sorted array to find:
    • If the element already exists (in which case, do nothing, as sets don’t allow duplicates).
    • The correct position where the new element should be inserted to maintain sorted order.
  3. Make Space: If the element is new, use memmove to shift all elements from the insertion point to the end, one position to the right. This creates a gap for the new element.
  4. Insert Element: Write the new element into the newly created space.
  5. Update Length: Increment the Intset’s length field.

Deleting an Element

  1. Binary Search: Perform a binary search to find the position of the element to be deleted.
  2. Shift Elements: If the element is found, use memmove to shift all elements from the position after the deleted element, one position to the left. This effectively overwrites the deleted element and closes the gap.
  3. Update Length: Decrement the Intset’s length field.

Optimized Set Operations

Because Intset stores elements in a sorted array, common set operations like Union and Intersection can be highly optimized. Similar to the merge step in merge sort, you can iterate through two sorted Intsets with two pointers, comparing elements to efficiently find common elements (intersection) or combine them (union) in O(N) time, where N is the total number of elements, significantly faster than hash table-based approaches for large sets.

Source Code Insights (Redis Internals)

Understanding the actual Redis source code provides deeper clarity into these mechanisms.

  • s_add Command: The entry point for adding elements to a set. This command eventually dispatches to functions that handle the underlying data structure.
  • set_type_create: This function is crucial for determining the initial type of set (hash table or Intset) based on the first element’s representability as a long long.
  • intsetNew: Responsible for allocating the initial memory for a new Intset object.
  • intsetSearch: This function implements the binary search algorithm. You’ll find the classic while (max >= min) loop, calculating mid, and comparing values to efficiently locate elements or insertion points. It often takes a pointer to return the position, avoiding redundant lookups.
  • intsetAdd: This function encapsulates the logic for adding an element, including checking for existing elements, handling encoding upgrades, resizing the Intset if necessary, and finally inserting the element using memmove.

Memory Allocation Strategy

Redis doesn’t just allocate the exact memory needed. For Intsets (and other dynamic structures), it often pre-allocates memory with a buffer. When the current capacity is reached, it reallocates a larger chunk of memory (e.g., doubling the size) to reduce the frequency of reallocations, which are expensive operations.

Key Takeaways

  • Specialized Data Structures: Redis uses specialized, highly optimized data structures like Intset for specific data types (integers) to achieve superior memory efficiency and performance.
  • Dynamic Adaptation: Redis dynamically switches between Intset and hash table implementations based on the elements stored, ensuring optimal performance and memory usage.
  • Memory Frugality: Intset’s dynamic encoding (16-bit, 32-bit, 64-bit) and sorted array structure demonstrate Redis’s commitment to minimizing memory footprint.
  • Trade-offs: While Intset offers O(log N) search, its O(N) insertion/deletion operations for large sets necessitate the set-max-intset-entries configuration to prevent performance bottlenecks, leading to a switch to hash tables.
  • Internals Matter: Understanding these internal mechanisms is crucial for appreciating Redis’s design philosophy and for optimizing applications that interact with it.

This detailed look into Intset showcases Redis’s engineering elegance in balancing performance, memory efficiency, and flexibility for various use cases.

Arpit Bhayani

Principal Engineer II at Razorpay - building Agent Studio, Ex-staff engg at GCP Memorystore & Dataproc, Creator of DiceDB, ex-Amazon Fast Data, ex-Director of Engg. SRE and Data Engineering at Unacademy. I spark engineering curiosity through my no-fluff engineering videos on YouTube and my courses