Red-Black Trees
memory optimization
data structures
color information storage
computer science

How to save the memory when storing color information in Red-Black Trees?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A Red-Black Tree needs one extra piece of metadata per node: the color bit. That sounds tiny, but in memory-sensitive systems or large trees, the way that bit is stored can still matter because structure padding and alignment can turn a one-bit concept into multiple wasted bytes.

Why the Color Field Can Cost More Than One Bit

A naive implementation often stores color as a bool or enum inside every node.

c
1typedef enum {
2    RED,
3    BLACK
4} Color;
5
6typedef struct Node {
7    int key;
8    Color color;
9    struct Node* left;
10    struct Node* right;
11    struct Node* parent;
12} Node;

The problem is not only the Color field itself. Compilers may insert padding so the full node stays properly aligned. On a pointer-heavy structure, that can mean the color metadata effectively costs more than one byte.

Pack the Color into an Existing Pointer

A classic optimization is pointer tagging. Because node pointers are usually aligned, their least significant bits are often unused. One of those bits can store the color.

c
1#include <stdint.h>
2#include <stdbool.h>
3
4#define RED_BIT ((uintptr_t)1)
5
6typedef struct Node {
7    int key;
8    struct Node* left;
9    struct Node* right;
10    uintptr_t parent_and_color;
11} Node;
12
13static inline Node* get_parent(Node* n) {
14    return (Node*)(n->parent_and_color & ~RED_BIT);
15}
16
17static inline bool is_red(Node* n) {
18    return (n->parent_and_color & RED_BIT) != 0;
19}
20
21static inline void set_parent(Node* n, Node* p) {
22    uintptr_t color = n->parent_and_color & RED_BIT;
23    n->parent_and_color = (uintptr_t)p | color;
24}
25
26static inline void set_red(Node* n, bool red) {
27    uintptr_t parent = n->parent_and_color & ~RED_BIT;
28    n->parent_and_color = parent | (red ? RED_BIT : 0);
29}

This saves a dedicated color field and can improve total node density in memory.

Why Pointer Tagging Works

If a pointer is aligned to at least two bytes, the lowest bit is always zero for a valid address. That makes it available for metadata. Many allocators align objects to larger boundaries, so sometimes more than one low bit is free.

For Red-Black Trees, you only need one bit:

  • '0 for black'
  • '1 for red'

This is enough to encode the color without increasing the logical size of the node.

Alternatives to Pointer Tagging

Pointer tagging is efficient, but not always the best choice. Other techniques include:

  • Store color in a uint8_t and reorder fields to reduce padding
  • Use sentinel NIL nodes to simplify edge cases and sometimes reduce auxiliary state
  • Store colors in a side array when nodes live in an indexed arena rather than as standalone heap objects

For example, field reordering can already help:

c
1typedef struct Node {
2    struct Node* left;
3    struct Node* right;
4    struct Node* parent;
5    int key;
6    unsigned char color;
7} Node;

This does not remove the color field, but depending on the platform, it can reduce padding waste compared with a poor field order.

Tradeoffs of the Memory-Saving Approach

The smaller representation is not free. Packed metadata makes code harder to read and easier to get wrong during rotations or rebalancing.

You should consider pointer tagging when:

  • The tree is very large
  • Per-node overhead matters
  • You are writing low-level C or C++ code
  • You can enforce alignment assumptions safely

You should avoid it when:

  • Maintainability matters more than a few saved bytes
  • The language runtime abstracts raw pointers away
  • Portability across unusual architectures is a concern

Measure Before and After

Memory optimization only matters if it actually changes the footprint. Benchmark with realistic node counts and inspect sizeof(Node) before deciding.

c
1#include <stdio.h>
2
3int main(void) {
4    printf("Node size: %zu\n", sizeof(Node));
5    return 0;
6}

If the packed design does not reduce sizeof(Node) on your target platform, the added complexity may not be justified.

Common Pitfalls

A common mistake is assuming bool color; costs only one bit. The compiler stores it as at least one byte, and the surrounding padding may increase total node size even more.

Another mistake is using pointer tagging without understanding alignment guarantees. If your assumptions are wrong, masking bits can corrupt addresses.

A third mistake is optimizing the color field while ignoring bigger memory consumers such as allocator overhead, key payload size, or separate metadata stored elsewhere.

Summary

  • A Red-Black Tree needs only one logical color bit, but naive storage can waste extra memory.
  • Pointer tagging is a classic way to store color inside an aligned pointer.
  • Field reordering or arena-based side metadata can also reduce overhead.
  • Check sizeof(Node) before and after optimization to verify the benefit.
  • Use packed metadata only when the memory savings justify the added complexity.

Course illustration
Course illustration

All Rights Reserved.