Skip to content
Home » Software Design » Coupling and Cohesion in C — A Deep Dive With Examples

Coupling and Cohesion in C — A Deep Dive With Examples

Coupling and Cohesion featured image with purple background, Principles badge, CC icon, Deep Dive in C subtitle in the Software Design in C series by NerdyElectronics
Software Design Principles in C
Part 18 of 31View Full Path →

KEY TAKEAWAYS

  • Coupling = how much modules depend on each other (lower is better)
  • Cohesion = how focused a single module is (higher is better)
  • Reduce coupling with callbacks, function pointers, and clean interfaces
  • Improve cohesion by splitting “god modules” into focused, single-purpose files
  • The combination of low coupling + high cohesion produces maintainable, testable, and reusable code

What Are Coupling and Cohesion?

Coupling measures how much one module depends on another. Low coupling means modules can change independently. High coupling means a change in one module forces changes in others.Cohesion measures how closely the elements within a single module are related. High cohesion means everything in the module serves one clear purpose. Low cohesion means unrelated things are packed together.The goal: high cohesion within modules, low coupling between modules.

Coupling: From Worst to Best

Content Coupling (Worst) — Reaching into Another Module’s Internals

// module_a.c — directly accesses module_b's internal variable
extern int module_b_internal_counter;  // BAD: knows about internal state

void module_a_process(void) {
    module_b_internal_counter++;  // Directly modifying another module's state
}
If module_b renames or removes that variable, module_a breaks.

Common Coupling (Bad) — Sharing Global Variables

// globals.h
extern int shared_buffer[256];
extern int shared_index;

// module_a.c
void producer(void) {
    shared_buffer[shared_index++] = read_sensor();
}

// module_b.c
void consumer(void) {
    int val = shared_buffer[--shared_index];
    process(val);
}
Both modules depend on the same global state. Race conditions, hidden dependencies, and hard-to-trace bugs are common.

Data Coupling (Good) — Passing Only What’s Needed

// module_a.c
int read_sensor_value(void) {
    return adc_read(CHANNEL_0);
}

// module_b.c
void process_value(int value) {
    if (value > THRESHOLD) {
        activate_alarm();
    }
}

// main.c — modules communicate through simple data
int val = read_sensor_value();
process_value(val);
Each module exposes a clean function interface. They communicate through parameters and return values — no shared state.

Example 1: Reducing Coupling

Bad — Tight Coupling via Direct Calls

// alarm.c — directly depends on 3 other modules
#include "lcd.h"
#include "buzzer.h"
#include "network.h"

void trigger_alarm(int code) {
    lcd_print("ALARM: %d", code);
    buzzer_on(FREQ_2KHZ);
    network_send_alert(code);
}
The alarm module depends on LCD, buzzer, AND network. If any of those modules change their API, alarm.c breaks. You cannot test the alarm without all three dependencies.

Good — Low Coupling via Callbacks

// alarm.h
typedef void (*AlarmHandler)(int code);
void alarm_register(AlarmHandler handler);
void trigger_alarm(int code);

// alarm.c — knows nothing about LCD, buzzer, or network
static AlarmHandler handlers[8];
static int handler_count = 0;

void alarm_register(AlarmHandler h) {
    handlers[handler_count++] = h;
}

void trigger_alarm(int code) {
    for (int i = 0; i < handler_count; i++) {
        handlers[i](code);
    }
}

// main.c — wire up at startup
alarm_register(lcd_alarm_handler);
alarm_register(buzzer_alarm_handler);
alarm_register(network_alarm_handler);
The alarm module has zero #includes of other modules. It does not know or care what reacts to an alarm. Each responder module handles its own logic independently.

Cohesion: From Worst to Best

Coincidental Cohesion (Worst) — Unrelated Functions in One File

// utils.c — a dumping ground
void parse_temperature(const char *s, float *out);
void format_date(int y, int m, int d, char *buf);
int  calculate_checksum(const uint8_t *data, int len);
void toggle_led(int pin);
void send_http_request(const char *url);
These functions have nothing in common. They were grouped only because someone needed a place to put them.

Functional Cohesion (Best) — One Purpose Per Module

// temperature.c — everything related to temperature
void  temp_init(void);
float temp_read(void);
float temp_to_fahrenheit(float celsius);
bool  temp_is_critical(float celsius);
void  temp_calibrate(float offset);

// checksum.c — everything related to data integrity
uint8_t  crc8(const uint8_t *data, int len);
uint16_t crc16(const uint8_t *data, int len);
uint32_t crc32(const uint8_t *data, int len);
bool     verify_checksum(const uint8_t *data, int len, uint32_t expected);
Each module has one clear purpose. If you need temperature logic, you look in temperature.c. If you need checksums, you look in checksum.c. Everything in each file is related.

Example 2: Improving Cohesion

Bad — Low Cohesion “God Module”

// system.c — does everything
void system_read_sensors(void);
void system_update_display(void);
void system_check_alarms(void);
void system_send_telemetry(void);
void system_handle_button(void);
void system_write_log(void);
void system_run_diagnostics(void);

Good — High Cohesion After Splitting

// sensor.c
void sensor_read_all(SensorData *data);

// display.c
void display_update(const SensorData *data);

// alarm.c
void alarm_check(const SensorData *data);

// telemetry.c
void telemetry_send(const SensorData *data);

// input.c
void input_handle_button(void);

// logger.c
void logger_write(const char *msg);

// diagnostics.c
void diagnostics_run(void);
Each module is focused, testable, and has one reason to change.

Quick Reference

MetricBadGood
CouplingModules share globals, include each other’s .c files, access internal variablesModules communicate through clean function interfaces and callbacks
Cohesion“utils.c” with unrelated functions, god modules doing everythingEach file has one clear purpose, all functions within are related

Key Takeaways

  • Coupling = how much modules depend on each other (lower is better)
  • Cohesion = how focused a single module is (higher is better)
  • Reduce coupling with callbacks, function pointers, and clean interfaces
  • Improve cohesion by splitting “god modules” into focused, single-purpose files
  • The combination of low coupling + high cohesion produces maintainable, testable, and reusable code

📖 Related: Code Smells in C: Change Preventers (Duplicate Code, Tight Coupling, and More)

Leave a Reply

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