Table of Contents
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
| Metric | Bad | Good |
|---|---|---|
| Coupling | Modules share globals, include each other’s .c files, access internal variables | Modules communicate through clean function interfaces and callbacks |
| Cohesion | “utils.c” with unrelated functions, god modules doing everything | Each 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)

Vivek Bhageria — Lead Firmware R&D Engineer, 12+ years. Ex-Bosch (automotive powertrain), MusicTribe (real-time audio), medical devices. M.Tech BITS Pilani. I write at NerdyElectronics — practical, register-level embedded systems for engineers who want to understand what’s actually happening under the hood.







