Practical Coding Patterns in C
January 01, 2026
Coding patterns are extremely useful for reusable code writing styles. They are just repeatable ways to structure code so you don’t reinvent the wheel every time you solve a familiar problem.
Below are a few foundational patterns, each with two small C examples to show how they look in practice.
1. The Singleton Pattern
This one ensures that only one instance of an object ever exists.
LoggerExample.c
#include <stdio.h>
FILE* get_logger() {
static FILE* logger = NULL;
if (!logger) { // Only initialize once
logger = fopen("app.log", "a");
}
return logger;
}
GlobalConfigFile.c
typedef struct {
int debug;
int max_connections;
} Config;
Config* get_config() {
static Config cfg = {0};
static int initialized = 0;
if (!initialized) {// Only initialize once
cfg.debug = 1;
cfg.max_connections = 10;
initialized = 1;
}
return &cfg;
}
The pattern keeps initialization predictable and ensures the rest of your program doesn’t accidentally create competing versions of the same resource.
2. The Strategy Pattern
Whenever behavior is needed to change during runtime, this method is used in C with function pointers.
SortingStrategy.c
void bubble_sort(int* arr, int n);
void quick_sort(int* arr, int n);
void sort(int* arr, int n, void (*strategy)(int*, int)) {// Higher-order function
strategy(arr, n);
}
DynamicComparison.c
int compare_asc(int a, int b) { return a - b; }
int compare_desc(int a, int b) { return b - a; }
int find_best(int* arr, int n, int (*cmp)(int, int)) {// Higher-order function
int best = arr[0];
for (int i = 1; i < n; i++) {
if (cmp(arr[i], best) > 0) {
best = arr[i];
}
}
return best;
}
The strategy pattern keeps things flexible and allows less use of conditionals throughout the code.
3. The Observer Pattern
When parts of the program depend on constant changes on other areas of the program, like sensors, updates, UI events, the observer pattern is unique in the way it keeps things decoupled.
EventSub.c
#define MAX_OBSERVERS 8
void (*observers[MAX_OBSERVERS])(int);
int observer_count = 0;
void subscribe(void (*fn)(int)) {
observers[observer_count++] = fn;
}
void notify(int value) {
for (int i = 0; i < observer_count; i++)
observers[i](value);
}
TemperatureSensor.c
void on_temp_change(int temp) {
printf("Temperature changed: %d\n", temp);
}
void read_sensor() {
int temp = read(); // pretend we read hardware
notify(temp);
}
Observers allow behaviour on event detection without needing to rewrite the original's function logic.
4. The Factory Pattern
Helpful for creating objects with different specific configurations, it centralizes the creation logic.
ShapeFactory.c
typedef struct { int w, h; } Rect;
typedef struct { int r; } Circle;
enum ShapeType { SHAPE_RECT, SHAPE_CIRCLE };
void* create_shape(enum ShapeType type) {
switch (type) {
case SHAPE_RECT: return malloc(sizeof(Rect));
case SHAPE_CIRCLE: return malloc(sizeof(Circle));
}
return NULL;
}
MessageFactory.c
typedef struct { int id; char data[32]; } Message;
Message* create_message(int id) {
Message* m = malloc(sizeof(Message));
m->id = id;
snprintf(m->data, sizeof(m->data), "Message %d", id);
return m;
}
Factories keep construction consistent and prevent scattered initialization logic.
Patterns reduce overall friction when done correctly. They help make code easier to expand upon, helps rewrite code much less and makes your intentions known when documented well.