Table of Contents
KEY TAKEAWAYS
- Only talk to your immediate friends — avoid chained struct access like
a->b->c->d - Each layer should expose functions that operate on its own data
- Pass only the data a function actually needs, not the entire parent struct
- The result is code that is more modular, testable, and resilient to change
What Is the Law of Demeter?
The Law of Demeter (LoD), also called the Principle of Least Knowledge, states: a function should only talk to its immediate friends, not to strangers. In practice, this means a function should only access:- Its own local variables
- Its parameters
- Members of structs it directly owns
a->b->c->value violate the Law of Demeter because the calling code depends on the entire chain of internal structure.Example 1: Accessing Nested Hardware State
Bad — Reaching Through Struct Chains
typedef struct {
int raw_value;
float calibration_offset;
} ADC;
typedef struct {
ADC *adc;
int channel;
} Sensor;
typedef struct {
Sensor *sensors[8];
int sensor_count;
} SensorHub;
// Violates LoD — reaches 3 levels deep
float get_calibrated_reading(SensorHub *hub, int idx) {
int raw = hub->sensors[idx]->adc->raw_value;
float offset = hub->sensors[idx]->adc->calibration_offset;
return (float)raw * 0.01f + offset;
}
// What breaks:
// - If ADC struct changes, this function changes
// - If Sensor changes how it stores its ADC, this function changes
// - Tightly coupled to the ENTIRE internal structureGood — Ask, Don’t Dig
// Each level exposes only what it should
float adc_read_calibrated(const ADC *adc) {
return (float)adc->raw_value * 0.01f + adc->calibration_offset;
}
float sensor_read(const Sensor *s) {
return adc_read_calibrated(s->adc);
}
float hub_read_sensor(const SensorHub *hub, int idx) {
return sensor_read(hub->sensors[idx]);
}
// Caller only talks to its immediate friend
float temp = hub_read_sensor(hub, 3);Each function only accesses one level. If the ADC struct changes, only adc_read_calibrated needs updating. The hub and the application code are completely insulated.Example 2: Configuration Access
Bad — Deeply Coupled Config Access
typedef struct {
int baud_rate;
int parity;
} UartConfig;
typedef struct {
UartConfig uart;
int retry_count;
} CommConfig;
typedef struct {
CommConfig comm;
int log_level;
} SystemConfig;
// This function "knows" the entire config structure
void init_uart(SystemConfig *cfg) {
uart_set_baud(cfg->comm.uart.baud_rate); // 3 levels deep
uart_set_parity(cfg->comm.uart.parity); // 3 levels deep
}Good — Pass Only What Is Needed
// Pass the specific config the function needs
void init_uart(const UartConfig *cfg) {
uart_set_baud(cfg->baud_rate);
uart_set_parity(cfg->parity);
}
// Caller extracts the right level
void system_init(SystemConfig *cfg) {
init_uart(&cfg->comm.uart); // Only system_init knows the structure
}The init_uart function only knows about UartConfig. It does not know it came from a SystemConfig. This makes it reusable with any UartConfig source and testable in isolation.Why the Law of Demeter Matters
- Reduces coupling — changing internal struct layout only affects one layer
- Improves testability — functions that accept minimal parameters are easier to test
- Makes refactoring safe — you can restructure internals without cascading changes
- Clarifies responsibility — each module is responsible for its own data access
When to Relax the Rule
- Data transfer structs — simple structs used only to carry data (like a
Pointwithx, y) are fine to access directly - Performance-critical code — adding wrapper functions in tight loops may not be worth the overhead in embedded systems
- Fluent-style APIs — chained method calls are common in some designs and acceptable when the chain is the API contract
Key Takeaways
- Only talk to your immediate friends — avoid chained struct access like
a->b->c->d - Each layer should expose functions that operate on its own data
- Pass only the data a function actually needs, not the entire parent struct
- The result is code that is more modular, testable, and resilient to change

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.







