5 min read

Beyond Standards: Why Engineering Teams Should Focus on Cognitive Load

Most engineering teams I’ve worked with have spent at least a little time debating coding standards. Should we use tabs or spaces? How should we structure our directories? Which naming convention is best? While these discussions can feel productive, they often miss the bigger picture: the mental burden we place on developers trying to understand and maintain our codebases.

I recently came across a thoughtful repository by zakirullin that perfectly captures this issue. Rather than focusing on superficial preferences that don’t make or break projects, we should be thinking about cognitive load; the mental effort required to understand, modify, and maintain our code.

What is Cognitive Load in Software Development?

Cognitive load refers to the amount of mental effort required to process information and perform tasks. In software development, this translates to how much mental energy a developer needs to expend to understand a codebase, make changes, or debug issues.

High cognitive load manifests in several ways:

  • Developers spend more time understanding code than writing it
  • Simple changes require tracing through multiple layers of abstraction
  • New team members struggle to onboard effectively
  • Bug fixes become increasingly complex and time-consuming

What Actually Increases Cognitive Load

The real culprits aren’t formatting inconsistencies, but deeper structural issues. Complex abstractions, excessive layering, and scattered business logic force developers to hold too many concepts in their heads simultaneously. When code requires extensive mental mapping to understand, even simple changes become cognitively expensive.

The Problem with Standards-First Thinking

Traditional engineering standards documents often focus on:

  • File structure and organization
  • Naming conventions and casing
  • Tooling preferences and configurations
  • Code formatting rules

While these elements have their place, they’re often symptoms of a deeper issue rather than solutions to real problems. A perfectly formatted codebase with consistent naming can still be cognitively overwhelming if it’s built on complex abstractions or unclear business logic.

Why Consistency Matters (But Isn’t Enough)

It’s worth noting that consistency across a codebase does reduce cognitive load, and that’s actually the underlying intention of most coding standards. When developers can rely on consistent patterns, naming conventions, and file structures, they spend less mental energy figuring out “how things work here” and more time solving actual problems.

The issue isn’t that consistency is bad; it’s that consistency alone isn’t enough. We need to think beyond surface-level uniformity and consider the deeper cognitive burden of understanding what the code actually does, not just how it’s formatted.

A Better Approach

Instead of starting with standards documents, focus on making code easier to understand. Write for clarity over cleverness, minimise the mental models developers need to juggle, and structure business logic so it’s obvious rather than hidden. The goal is code that a new team member can understand and contribute to quickly.

One common mistake I’ve witnessed is over-abstraction in the name of DRY (Don’t Repeat Yourself). Teams often create complex webs of classes and methods to eliminate any code duplication, resulting in hundreds of lines of indirection when copying a few lines of code would have been perfectly acceptable.

Consider this example: a team needs to send welcome emails for different user types (customers, vendors, employees). The DRY approach might create an abstract EmailSender class with generic methods:

class EmailSender {
  send(type: string, data: any) {
    const template = this.getTemplate(type);
    const content = this.processTemplate(template, data);
    return this.deliver(content, this.getRecipients(type, data));
  }
  
  private getTemplate(type: string) { /* complex template resolution */ }
  private processTemplate(template: any, data: any) { /* generic processing */ }
  private getRecipients(type: string, data: any) { /* complex recipient logic */ }
}

// Usage becomes complex and unclear:
const emailSender = new EmailSender();
emailSender.send('customer_welcome', { 
  name: customer.name, 
  email: customer.email,
  type: 'customer'
});
emailSender.send('vendor_welcome', { 
  companyName: vendor.companyName, 
  contactEmail: vendor.contactEmail,
  type: 'vendor'
});

While this eliminates duplication, it forces developers to understand the entire abstraction to make simple changes. The usage is cryptic - what does 'customer_welcome' map to? What data structure is expected? A more cognitively friendly approach might be:

function sendCustomerWelcome(customer: Customer) {
  const email = {
    to: customer.email,
    subject: "Welcome to our store!",
    body: `Hi ${customer.name}, thanks for joining us!`
  };
  return emailService.send(email);
}

function sendVendorWelcome(vendor: Vendor) {
  const email = {
    to: vendor.contactEmail,
    subject: "Welcome to our vendor network",
    body: `Hi ${vendor.companyName}, let's get started!`
  };
  return emailService.send(email);
}

The second approach has more repetition, but each function is self-contained and immediately understandable. While DRY has its place, the cognitive cost of understanding abstracted code often outweighs the benefits of avoiding duplication. Sometimes a bit of repetition is the more maintainable choice.

The Bottom Line

Standards documents have their place, but they shouldn’t be the primary focus of engineering teams. By prioritizing cognitive load reduction, we create codebases that are easier to understand, maintain, and extend. This leads to better developer experience, faster feature delivery, and more sustainable engineering practices.