Skip to content
Home » Software Design » KISS Principle (Keep It Simple, Stupid) in C

KISS Principle (Keep It Simple, Stupid) in C

KISS Principle featured image with purple background, Clean Code badge, K icon, Keep It Simple in C subtitle in the Software Design in C series by NerdyElectronics
Software Design Principles in C
Part 8 of 31View Full Path →

KEY TAKEAWAYS

  • KISS: the simplest solution that meets the requirements is the best solution
  • Avoid dynamic memory allocation when a fixed-size buffer works
  • Avoid abstractions when you only have one concrete implementation
  • Write code for the reader, not to show off — you will be the reader in 6 months
  • If you cannot explain your code to a colleague in 30 seconds, it is too complex

What Is the KISS Principle?

KISS stands for Keep It Simple, Stupid. It means: prioritize simplicity; avoid unnecessary complexity. The simplest solution that works correctly is usually the best one.Clever code impresses no one during a 3 AM debugging session. Simple code does.

Example 1: Over-Engineered Config Parser

Bad: Dynamic memory, linked lists, and generic parsing for a 5-setting config

/* Over-engineered: dynamic allocation, linked list, generic parser */
typedef struct config_node {
    char *key;
    char *value;
    struct config_node *next;
} config_node_t;

config_node_t *config_head = NULL;

int config_parse(const char *filename) {
    FILE *f = fopen(filename, "r");
    if (!f) return -1;
    char line[128];
    while (fgets(line, sizeof(line), f)) {
        config_node_t *node = malloc(sizeof(config_node_t));
        node->key = malloc(32);
        node->value = malloc(32);
        sscanf(line, "%31[^=]=%31s", node->key, node->value);
        node->next = config_head;
        config_head = node;
    }
    fclose(f);
    return 0;
}

const char *config_get(const char *key) {
    config_node_t *n = config_head;
    while (n) {
        if (strcmp(n->key, key) == 0) return n->value;
        n = n->next;
    }
    return NULL;
}
/* Plus you need config_free() to avoid memory leaks... */

Good: Simple struct, simple parsing, no dynamic memory

/* Simple: fixed struct, no allocation, no linked list */
typedef struct {
    int   baud_rate;
    int   sensor_interval_ms;
    int   alarm_threshold;
    int   log_enabled;
    char  device_name[32];
} config_t;

config_t config = {
    .baud_rate          = 9600,     /* Defaults */
    .sensor_interval_ms = 1000,
    .alarm_threshold    = 80,
    .log_enabled        = 1,
    .device_name        = "Sensor-01",
};

int config_load(const char *filename) {
    FILE *f = fopen(filename, "r");
    if (!f) return -1;  /* Use defaults */
    char line[128];
    while (fgets(line, sizeof(line), f)) {
        sscanf(line, "baud_rate=%d",          &config.baud_rate);
        sscanf(line, "sensor_interval=%d",    &config.sensor_interval_ms);
        sscanf(line, "alarm_threshold=%d",    &config.alarm_threshold);
        sscanf(line, "log_enabled=%d",        &config.log_enabled);
        sscanf(line, "device_name=%31s",      config.device_name);
    }
    fclose(f);
    return 0;
}
No malloc, no free, no linked list, no generic lookup function. The config struct is directly accessible with config.baud_rate. Simple, fast, and impossible to leak memory.

Example 2: Clever One-Liner vs Clear Code

Bad: Compressed, “clever” code

/* What does this do? */
int r = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);

/* Or this? */
val = val  mx ? mx : val;

Good: Clear, readable code

/* Find maximum of three values */
int max_of_three(int a, int b, int c) {
    int max = a;
    if (b > max) max = b;
    if (c > max) max = c;
    return max;
}

/* Clamp value to range [0, max] */
int clamp_positive(int val, int max_val) {
    if (val  max_val) return max_val;
    return val;
}
The clear version takes a few more lines but can be understood instantly. It is also easier to set breakpoints and step through in a debugger.

KISS in Practice

Complex ApproachKISS Approach
Generic callback-driven state machine for 3 statesSimple switch-case with 3 cases
Dynamic array with realloc for a fixed-size listFixed-size array with a count variable
Custom string libraryStandard snprintf and strncpy
Thread pool for 2 tasksTwo dedicated threads (or a simple super-loop)

Key Takeaways

  • KISS: the simplest solution that meets the requirements is the best solution
  • Avoid dynamic memory allocation when a fixed-size buffer works
  • Avoid abstractions when you only have one concrete implementation
  • Write code for the reader, not to show off — you will be the reader in 6 months
  • If you cannot explain your code to a colleague in 30 seconds, it is too complex

Example 3: Over-Engineered String Comparison

Developers sometimes write elaborate solutions for simple problems. Here is a case where a straightforward task gets buried under unnecessary complexity.

Bad — Reinventing the Wheel

int strings_match(const char *a, const char *b) {
    int len_a = 0, len_b = 0;
    const char *pa = a, *pb = b;

    // First pass: compute lengths
    while (*pa++) len_a++;
    while (*pb++) len_b++;

    // Early exit on length mismatch
    if (len_a != len_b) return 0;

    // Second pass: compare character by character
    for (int i = 0; i < len_a; i++) {
        if (a[i] != b[i]) return 0;
    }
    return 1;
}
This is 15 lines that iterate over both strings twice. It is correct, but it ignores a well-tested standard library function that does exactly this.

Good — Use What Exists

int strings_match(const char *a, const char *b) {
    return strcmp(a, b) == 0;
}
One line. Universally understood. Tested by millions of programs. strcmp is part of the C standard library — no need to rewrite it.

How to Recognize Violations of KISS

Ask yourself these questions when reviewing code:
  • Can I explain this function in one sentence? If you need a paragraph, it is probably too complex.
  • Does this use a feature just because it is clever? Bitwise tricks, macro magic, and pointer arithmetic have their place — but only when they genuinely improve performance or clarity.
  • Would a junior developer understand this in under a minute? Code is read far more often than it is written. Optimize for the reader.
  • Am I solving a problem that does not exist yet? That overlaps with YAGNI, but it is a common source of unnecessary complexity.

KISS in Architecture

KISS applies beyond individual functions. At the system level:
  • Prefer flat module structures over deeply nested hierarchies
  • Use direct function calls before reaching for callback tables or event systems
  • Start with a simple if/else before building a rule engine
  • Use arrays before linked lists unless you genuinely need O(1) insertion
The simplest solution that meets the requirements is almost always the best starting point. You can add complexity later when the need is proven — removing it is much harder.

📖 Related: Dynamic Memory Allocation in C

Leave a Reply

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