Skip to content
Home » Software Design » Strategy Pattern in C — With Practical Examples

Strategy Pattern in C — With Practical Examples

Strategy Pattern featured image with purple background, Design Patterns badge, St icon, in C With Examples subtitle in the Software Design in C series by NerdyElectronics
Software Design Principles in C
Part 14 of 31View Full Path →

KEY TAKEAWAYS

  • The Strategy pattern encapsulates algorithms behind function pointers so they can be swapped
  • The calling code is independent of the specific algorithm used
  • Bundle related function pointers in a struct when the strategy has multiple operations
  • This pattern is the foundation of C’s qsort(), bsearch(), and many embedded driver APIs

What Is the Strategy Pattern?

The Strategy pattern lets you define a family of algorithms, put each one behind the same interface, and swap them at runtime. In C, this means using function pointers to select which algorithm runs without changing the code that calls it.Instead of hardcoding one approach, you make the algorithm a parameter. The calling code says what to do; the strategy decides how.

Example 1: Sorting with Swappable Comparators

Bad — Hardcoded Sort Order

void sort_sensors_by_id(Sensor *arr, int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j  arr[j+1].id) {
                Sensor tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}

void sort_sensors_by_value(Sensor *arr, int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j  arr[j+1].value) {  // Only this line differs
                Sensor tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}
The entire sort function is duplicated — only the comparison line differs. Adding a third sort order means a third copy.

Good — Comparison Strategy

typedef int (*CompareFunc)(const Sensor *a, const Sensor *b);

void sort_sensors(Sensor *arr, int n, CompareFunc cmp) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j  0) {
                Sensor tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}

// Strategies
int compare_by_id(const Sensor *a, const Sensor *b) {
    return a->id - b->id;
}

int compare_by_value(const Sensor *a, const Sensor *b) {
    return (a->value > b->value) - (a->value value);
}

int compare_by_name(const Sensor *a, const Sensor *b) {
    return strcmp(a->name, b->name);
}

// Usage — swap strategy without changing sort code
sort_sensors(sensors, count, compare_by_id);
sort_sensors(sensors, count, compare_by_value);
sort_sensors(sensors, count, compare_by_name);
One sort function, multiple strategies. This is exactly how the C standard library’s qsort() works — it takes a comparator function pointer as a strategy.

Example 2: Data Compression Strategy

Bad — Switch on Compression Type

int compress_data(const uint8_t *in, int in_len, uint8_t *out, int type) {
    switch (type) {
    case COMPRESS_RLE:
        return rle_compress(in, in_len, out);
    case COMPRESS_LZ:
        return lz_compress(in, in_len, out);
    case COMPRESS_HUFF:
        return huffman_compress(in, in_len, out);
    default:
        return -1;
    }
    // New algorithm? Modify this function.
}

Good — Strategy via Function Pointer

typedef int (*CompressFunc)(const uint8_t *in, int in_len, uint8_t *out);

typedef struct {
    const char   *name;
    CompressFunc  compress;
    CompressFunc  decompress;
} CompressionStrategy;

// Define strategies
static const CompressionStrategy strategies[] = {
    { "RLE",     rle_compress,     rle_decompress     },
    { "LZ77",    lz_compress,      lz_decompress      },
    { "Huffman", huffman_compress,  huffman_decompress  },
};

// Use a strategy
void store_data(const uint8_t *data, int len, const CompressionStrategy *s) {
    uint8_t compressed[4096];
    int clen = s->compress(data, len, compressed);
    printf("Compressed with %s: %d -> %d bytesn", s->name, len, clen);
    flash_write(compressed, clen);
}

// Caller picks the strategy
store_data(sensor_data, data_len, &strategies[0]);  // RLE
store_data(sensor_data, data_len, &strategies[2]);  // Huffman
Adding a new compression algorithm means adding one entry to the strategies array. The store_data function never changes. Each strategy bundles both compress and decompress functions together.

Strategy Pattern vs. Function Pointers

Every strategy pattern uses function pointers, but not every function pointer is a strategy. The distinction:
  • Strategy: you have a family of interchangeable algorithms behind a common interface, and the caller selects which one to use
  • Simple callback: you have one hook point where the caller provides custom behavior
If you have multiple related function pointers bundled in a struct (like compress + decompress), that is a strong signal you are using the Strategy pattern.

When to Use the Strategy Pattern

  • Multiple algorithms for the same task — sorting, compression, hashing, filtering
  • Runtime selection — picking an algorithm based on configuration or input
  • Testing — swapping real implementations with test doubles
  • Avoiding switch/if chains that grow with each new variant

Key Takeaways

  • The Strategy pattern encapsulates algorithms behind function pointers so they can be swapped
  • The calling code is independent of the specific algorithm used
  • Bundle related function pointers in a struct when the strategy has multiple operations
  • This pattern is the foundation of C’s qsort(), bsearch(), and many embedded driver APIs

Leave a Reply

Your email address will not be published. Required fields are marked *