SpecThis logo

Logical Lines of Code (LOC)

Measuring the true length and complexity of functions

What is Logical LOC?

Logical Lines of Code (LOC) counts the number of executable statements in a function or method, excluding comments, blank lines, and braces. This gives a more accurate measure of function length than simply counting physical lines.

Long functions are harder to understand, test, and maintain. They often indicate that a function is trying to do too much and should be broken down into smaller, focused pieces.

How It's Calculated

What Counts as a Logical Line

✅ Counted:

  • Variable declarations
  • Assignment statements
  • Function calls
  • Return statements
  • if/else/while/for statements
  • Expression statements
  • throw statements

❌ Not Counted:

  • Comments (// or /* */)
  • Blank lines
  • Opening/closing braces
  • JSDoc documentation
  • Import/export statements (usually)

The calculation focuses on meaningful statements that perform actual logic, ignoring formatting and documentation.

Code Examples

Example 1: Logical LOC = 3

Count only the executable statements, not comments or blank lines.

/**
 * Calculate the area of a rectangle
 * This is JSDoc - doesn't count
 */
function calculateArea(width, height) {
  // This comment doesn't count

  const area = width * height;  // Statement 1
  console.log(area);            // Statement 2
  return area;                  // Statement 3
}

// Physical Lines: 11
// Logical LOC: 3

Example 2: Logical LOC = 8

Control structures count as statements.

function validateUser(user) {
  if (!user) {              // Statement 1: if condition
    return false;           // Statement 2: return
  }

  const hasEmail =          // Statement 3: variable declaration
    user.email && user.email.includes("@");

  if (!hasEmail) {          // Statement 4: if condition
    return false;           // Statement 5: return
  }

  const isAdult =           // Statement 6: variable declaration
    user.age >= 18;

  return isAdult;           // Statement 7: return
}

// Physical Lines: 17
// Logical LOC: 7

Example 3: Array Methods and Chaining

Method chains typically count as a single statement.

function getActiveUserNames(users) {
  return users                      // Statement 1: entire chain
    .filter(u => u.isActive)        // (part of statement 1)
    .map(u => u.name)               // (part of statement 1)
    .sort();                        // (part of statement 1)
}

// Physical Lines: 6
// Logical LOC: 1

// Compare to loop version:
function getActiveUserNamesLoop(users) {
  const result = [];                // Statement 1
  for (const user of users) {       // Statement 2
    if (user.isActive) {            // Statement 3
      result.push(user.name);       // Statement 4
    }
  }
  result.sort();                    // Statement 5
  return result;                    // Statement 6
}

// Physical Lines: 10
// Logical LOC: 6

Example 4: Logical LOC = 45 (Too Long)

Long functions become hard to understand and maintain.

function processOrder(order) {
  // Validate order - 10 statements
  if (!order) throw new Error("No order");                      // 1
  if (!order.items || order.items.length === 0) {               // 2
    throw new Error("No items");                                // 3
  }
  if (!order.customer) throw new Error("No customer");          // 4
  if (!order.customer.email) throw new Error("No email");       // 5

  // Calculate totals - 12 statements
  let subtotal = 0;                                             // 6
  for (const item of order.items) {                             // 7
    if (!item.price || !item.quantity) continue;                // 8
    subtotal += item.price * item.quantity;                     // 9
  }
  const tax = subtotal * 0.08;                                  // 10
  const shipping = subtotal > 100 ? 0 : 10;                     // 11
  const total = subtotal + tax + shipping;                      // 12

  // Apply discounts - 8 statements
  let discount = 0;                                             // 13
  if (order.customer.type === "premium") {                      // 14
    discount = total * 0.1;                                     // 15
  } else if (order.customer.type === "gold") {                  // 16
    discount = total * 0.05;                                    // 17
  }
  const finalTotal = total - discount;                          // 18

  // Update inventory - 6 statements
  for (const item of order.items) {                             // 19
    const product = findProduct(item.productId);                // 20
    if (product) {                                              // 21
      product.quantity -= item.quantity;                        // 22
      saveProduct(product);                                     // 23
    }
  }

  // Send notifications - 9 statements
  const notification = {                                        // 24
    to: order.customer.email,
    subject: "Order Confirmed",
    body: `Total: $${finalTotal}`,
  };
  try {                                                         // 25
    sendEmail(notification);                                    // 26
    logSuccess(order.id);                                       // 27
  } catch (error) {                                             // 28
    logError(error);                                            // 29
    throw error;                                                // 30
  }

  return { orderId: order.id, total: finalTotal };              // 31
}

// Logical LOC: ~31 (actual count may be higher with all statements)
// Way too long - should be split into multiple functions!

Example 5: Refactored (LOC = 5-8 per function)

Breaking into focused functions keeps each function short and clear.

// Main orchestrator: Logical LOC = 5
function processOrder(order) {
  validateOrder(order);                                 // 1
  const totals = calculateOrderTotals(order);           // 2
  updateInventory(order.items);                         // 3
  sendOrderConfirmation(order, totals.final);           // 4
  return { orderId: order.id, total: totals.final };    // 5
}

// Validation: Logical LOC = 5
function validateOrder(order) {
  if (!order) throw new Error("No order");                      // 1
  if (!order.items?.length) throw new Error("No items");        // 2
  if (!order.customer) throw new Error("No customer");          // 3
  if (!order.customer.email) throw new Error("No email");       // 4
}

// Calculate totals: Logical LOC = 8
function calculateOrderTotals(order) {
  const subtotal = order.items.reduce((sum, item) =>           // 1
    sum + (item.price * item.quantity), 0);

  const tax = subtotal * 0.08;                                  // 2
  const shipping = subtotal > 100 ? 0 : 10;                     // 3
  const discount = getDiscount(order.customer, subtotal);       // 4
  const final = subtotal + tax + shipping - discount;           // 5

  return { subtotal, tax, shipping, discount, final };          // 6
}

// Get discount: Logical LOC = 6
function getDiscount(customer, subtotal) {
  const discountRates = {                                       // 1
    premium: 0.1,
    gold: 0.05,
  };

  const rate = discountRates[customer.type] || 0;               // 2
  return subtotal * rate;                                       // 3
}

// Update inventory: Logical LOC = 6
function updateInventory(items) {
  for (const item of items) {                                   // 1
    const product = findProduct(item.productId);                // 2
    if (product) {                                              // 3
      product.quantity -= item.quantity;                        // 4
      saveProduct(product);                                     // 5
    }
  }
}

// Send notification: Logical LOC = 8
function sendOrderConfirmation(order, total) {
  const notification = {                                        // 1
    to: order.customer.email,
    subject: "Order Confirmed",
    body: `Total: $${total}`,
  };

  try {                                                         // 2
    sendEmail(notification);                                    // 3
    logSuccess(order.id);                                       // 4
  } catch (error) {                                             // 5
    logError(error);                                            // 6
    throw error;                                                // 7
  }
}

// Maximum LOC per function: 8
// Each function has a single, clear purpose!

Why Function Length Matters

Problems with Long Functions

  • Hard to understand at a glance
  • Difficult to name accurately
  • Usually doing too many things
  • Hard to test thoroughly
  • Difficult to reuse
  • Higher chance of bugs
  • Changes are riskier
  • Harder to review in PRs

Benefits of Short Functions

  • Easy to understand
  • Clear, descriptive names
  • Single responsibility
  • Simple to test
  • Highly reusable
  • Fewer bugs
  • Safe to modify
  • Quick to review

Recommended Thresholds

Sensitivity LevelMax LOCUse Case
High≤ 50Highly maintainable code, microservices
Medium≤ 150Most applications, balanced approach
Low≤ 300Legacy codebases, gradual improvement

The "Screenful" Rule

A good rule of thumb: if a function doesn't fit on your screen without scrolling, it's probably too long. Most developers can comfortably view 20-50 lines at once, which corresponds roughly to the "High" sensitivity threshold.

Refactoring Strategies

1. Extract Method

Take a section of code and move it to its own function with a descriptive name.

  • Look for code blocks that are commented
  • Find logical groupings of statements
  • Identify repeated code patterns

2. Compose Method

Make functions read like well-named steps in a process.

function checkout() {
  validateCart();
  calculateTotals();
  processPayment();
  updateInventory();
  sendConfirmation();
}

3. Replace Loop with Pipeline

Use array methods (filter, map, reduce) instead of explicit loops to reduce LOC while maintaining clarity.

4. Introduce Parameter Object

If a function has many parameters or local variables, it might be doing too much. Group related data into objects and extract helper functions.

Related Metrics

  • Nesting Depth: Long functions often have deep nesting
  • Branch Count: More lines usually means more decision points
  • File Line Count: Files with many long functions are large files