Expressive Code


Code as Communication#

Code has two audiences: computers and humans. While computers care only about correctness, humans need to understand intent, purpose, and reasoning. Expressive code optimizes for human comprehension without sacrificing computational efficiency.

Most developers underestimate how much time they spend reading code versus writing it. Studies suggest the ratio is roughly 10:1. Every minute invested in making code more expressive pays dividends across every future interaction with that code.

Expressive code eliminates cognitive friction. It communicates its purpose clearly, making the developer’s intent obvious to anyone reading it months or years later, including your future self.

The Cost of Unclear Code#

Unclear code creates compound costs that grow over time:

function calc(x, y, t) {
  if (t === 1) {
    return x + (y * 0.1);
  }
  return x + (y * 0.15);
}

This function works perfectly, but reveals nothing about its purpose. What do the parameters represent? Why are there two different multipliers? What business rule does this implement?

  • Debugging time increases: Developers must reverse-engineer intent from implementation
  • Change confidence decreases: Fear of breaking unknown behavior leads to cargo cult programming
  • Knowledge transfer fails: Business logic remains trapped in individuals’ heads
  • Testing becomes harder: Unclear code leads to unclear test cases

Principles of Expressive Code#

Choose Meaningful Names#

// Before: Cryptic
function calc(x, y, t) {
  if (t === 1) {
    return x + (y * 0.1);
  }
  return x + (y * 0.15);
}

// After: Expressive
function calculateTotalWithTax(subtotal, taxAmount, customerType) {
  const STANDARD_TAX_RATE = 0.15;
  const PREMIUM_TAX_RATE = 0.1;

  if (customerType === CustomerType.PREMIUM) {
    return subtotal + (taxAmount * PREMIUM_TAX_RATE);
  }
  return subtotal + (taxAmount * STANDARD_TAX_RATE);
}

Express Intent, Not Implementation#

// Before: Implementation-focused
const results = data.filter(item => item.status !== 'deleted')
                   .filter(item => item.createdAt > cutoffDate)
                   .map(item => item.id);

// After: Intent-focused
const activeRecordIds = getActiveRecordsSince(data, cutoffDate)
  .map(record => record.id);

function getActiveRecordsSince(records, cutoffDate) {
  return records.filter(isActiveRecord)
               .filter(record => isCreatedAfter(record, cutoffDate));
}

Use the Domain Language#

Let business concepts drive your naming and structure. If the business talks about “orders,” “customers,” and “shipments,” your code should use the same terms.

Make Illegal States Impossible#

// Before: Unclear state management
class Order {
  status: string;
  shipmentDate?: Date;
  trackingNumber?: string;
}

// After: Expressive state modeling
type OrderStatus = 
  | { type: 'pending' }
  | { type: 'confirmed' }
  | { type: 'shipped'; shipmentDate: Date; trackingNumber: string }
  | { type: 'delivered'; deliveredDate: Date };

The Power of Small Functions#

Expressive code often means breaking large functions into smaller, focused ones with clear names. This creates a vocabulary of operations that builds up to complex behavior.

// Before: Monolithic and unclear
function processOrder(order) {
  if (order.items.length === 0) throw new Error('Empty order');
  let total = 0;
  for (const item of order.items) {
    total += item.price * item.quantity;
  }
  if (order.customer.type === 'premium') total *= 0.9;
  if (total > 100) {
    // send to fulfillment
    console.log('Sending to fulfillment...');
  }
  return total;
}

// After: Expressive and composed
function processOrder(order) {
  validateOrderHasItems(order);
  const subtotal = calculateSubtotal(order.items);
  const total = applyCustomerDiscount(subtotal, order.customer);
  if (qualifiesForAutoFulfillment(total)) {
    sendToFulfillment(order);
  }
  return total;
}

Each function name tells a story. Reading the code becomes like reading a well-structured outline of the business process.

The Hierarchy of Expression#

There’s a hierarchy to how information should be expressed in code. Each level should handle what it does best, avoiding redundancy between levels:

Don’t use a comment when a variable name would suffice. Don’t use a variable name when a type would suffice.

// Poor: Comment explains what unclear code does
// User ID from the database
const id = getUserData().id;

// Better: Variable name explains what it contains
const userId = getUserData().id;

// Best: Type makes the constraint explicit
type UserId = string & { readonly brand: unique symbol };
const userId: UserId = getUserData().id as UserId;

Comments have a fundamental problem: they can lie. Code is always correct (it does exactly what it says), but comments can become outdated, misleading, or wrong. The compiler never checks comments for accuracy.

When you need comments, use them to explain why, not what. Provide context that can’t be expressed in code alone: business rules, historical decisions, or non-obvious constraints.

The Business Value of Expressiveness#

Expressive code isn’t just about developer convenience. It has direct business impact:

  • Faster feature development: Developers understand existing code quickly
  • Reduced bugs: Clear intent leads to correct implementations
  • Easier maintenance: Changes become safer when intent is obvious
  • Better onboarding: New team members become productive faster
  • Knowledge preservation: Business logic survives team changes

Key Insight: Expressive code reduces the cognitive load of development. When developers spend less time deciphering intent, they spend more time delivering value.

Common Anti-Patterns#

Premature Optimization#

Writing cryptic code for performance before measuring actual performance problems.

Clever Code#

Using language features in unexpected ways to show off rather than communicate clearly.

Generic Everything#

Using generic terms like “data,” “info,” “manager,” or “handler” instead of specific domain language.

Comment-Driven Development#

Relying on comments to explain what unclear code does instead of making the code self-explanatory.

Building Expressive Habits#

Expressiveness is a skill that improves with practice:

  • Read code aloud: If it sounds awkward, it probably needs better names
  • Write for your future self: Assume you’ll forget everything about this code in six months
  • Use pair programming: Explaining your code to someone else reveals unclear areas
  • Refactor regularly: As understanding grows, update names and structure to match
  • Learn from the domain: Spend time with business experts to understand their language

Conclusion#

Expressive code is an investment in your team’s future productivity. It takes slightly more effort upfront to choose good names and structure code clearly, but this investment pays dividends every time someone reads, modifies, or debugs that code.

The goal isn’t to write poetry. It’s to write code that communicates its intent so clearly that the next developer (including your future self) can understand and modify it confidently.

Start with one simple practice: before committing code, read it once more and ask yourself whether someone unfamiliar with the problem could understand what the code accomplishes just by reading it. If not, invest a few more minutes in making it expressive.

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” — Martin Fowler