Early Linting Prevents Technical Debt Ratcheting

James Phoenix
James Phoenix

Summary

Introducing linting late in a project forces you to either fix hundreds of accumulated violations or use ratcheting mechanisms that allow staged technical debt. Enable linting from day one and integrate it into your development workflow to prevent entropy accumulation. The small upfront cost saves exponential cleanup effort later.

The Problem

Introducing linting late in a project requires fixing accumulated violations across the entire codebase or implementing ratcheting systems that permit existing violations while preventing new ones. Both approaches introduce friction, technical debt, and ongoing maintenance burden that could have been avoided.

The Solution

Enable linting from project start and integrate it into your development workflow through hooks and CI/CD. This prevents technical debt accumulation by enforcing quality standards from the first line of code. Early linting acts as an information filter that reduces the entropy of your codebase over time.

The Problem

You’re three months into a project. The codebase has grown to 50,000 lines. Your team decides to introduce ESLint to improve code quality.

You run npx eslint . and see:

847 problems (623 errors, 224 warnings)
  412 errors and 89 warnings potentially fixable with the `--fix` option.

Now you face an uncomfortable choice:

Option 1: Fix Everything Now

  • Spend 2-3 days fixing 847 violations
  • Risk introducing bugs during mass cleanup
  • Block all other development work
  • Team morale suffers from “grunt work”

Option 2: Implement Ratcheting

  • Configure linting to only check changed files (e.g., lint-staged)
  • Allow existing violations to persist
  • Create ongoing maintenance burden
  • Technical debt continues to accumulate in untouched files

Option 3: Disable Strict Rules

  • Weaken linting rules to reduce violations
  • Compromise on code quality standards
  • Defeat the purpose of introducing linting

All three options are painful compromises that could have been avoided.

Why This Happens

Without linting, your codebase exists in a high-entropy state. Each developer makes independent formatting decisions, uses different patterns, and introduces inconsistencies. Over time, these small deviations compound:

Day 1:   5 violations
Week 1:  37 violations
Month 1: 184 violations
Month 3: 847 violations

Technical debt accumulates exponentially, not linearly. The longer you wait, the harder it becomes to enforce standards.

The Solution

Enable linting from project start—ideally during initial setup, before writing any application code.

Implementation Timeline

Day 0: Project Initialization

# Initialize project
npm init -y

# Install linting tools IMMEDIATELY
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier

# Generate config
npx eslint --init

Day 0: Configure Quality Standards

Create .eslintrc.json:

{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint", "prettier"],
  "rules": {
    "prettier/prettier": "error",
    "no-console": "warn",
    "no-unused-vars": "error",
    "@typescript-eslint/explicit-function-return-type": "warn"
  }
}

Day 0: Integrate with Development Workflow

Add to package.json:

{
  "scripts": {
    "lint": "eslint . --ext .ts,.tsx",
    "lint:fix": "eslint . --ext .ts,.tsx --fix",
    "format": "prettier --write \"**/*.{ts,tsx,json,md}\""
  }
}

Day 0: Automate with Hooks

Install and configure Husky + lint-staged:

npm install --save-dev husky lint-staged
npx husky init

Create .husky/pre-commit:

#!/usr/bin/env sh
npx lint-staged

Configure package.json:

{
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ]
  }
}

Day 0: Add CI/CD Gate

Create .github/workflows/lint.yml:

name: Lint

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run lint

Result: From the first commit, every line of code passes linting. Zero technical debt accumulates.

Why Early Linting Works

1. Zero Accumulation

Violations are caught and fixed immediately, before they compound:

Without Early Linting:
Day 15 violations
Month 3847 violations (169x growth)

With Early Linting:
Day 10 violations
Month 30 violations (0 growth)

2. Habit Formation

Developers learn quality standards from day one. They write compliant code naturally, not as an afterthought:

// Developer without linting habits
function getData(id) {  // No types, inconsistent formatting
  const data=db.get(id)
  return data
}

// Developer with linting habits (from day 1)
function getData(id: string): Promise<Data> {
  const data = await db.get(id);
  return data;
}

3. Reduced Cognitive Load

No decisions about formatting, import ordering, or code style. The linter decides:

// No debate needed - linter enforces
import { UserService } from './services/user';  // ✅ Alphabetical
import { AuthService } from './services/auth';  // ✅ Alphabetical

// Developer focuses on logic, not style
export async function createUser(data: UserData): Promise<User> {
  // Business logic here
}

4. Easier Code Review

Reviewers focus on logic, not formatting:

Without Linting:
- "Please add semicolons"
- "Indent with 2 spaces, not 4"
- "Remove unused imports"
- "Use const instead of let"

With Linting:
- "This function could use better error handling"
- "Consider extracting this logic into a separate service"
- "Are we handling the race condition here?"

5. Prevents Ratcheting Complexity

No need for complex tooling to manage “legacy violations”:

Udemy Bestseller

Learn Prompt Engineering

My O'Reilly book adapted for hands-on learning. Build production-ready prompts with practical exercises.

4.5/5 rating
306,000+ learners
View Course
# Without early linting - complex ratcheting setup
npm install --save-dev eslint-ratchet
# Configure baseline files
# Maintain separate rules for old vs new code
# Document exceptions and technical debt

# With early linting - simple setup
npm install --save-dev eslint
# Done. All code follows same rules.

The Mathematics: Entropy Accumulation Over Time

From information theory, entropy measures disorder in a system. Without quality gates, entropy increases over time.

Let’s model technical debt accumulation:

Violations per commit = $v$
Commits per day = $c$
Days = $d$

Total violations = $v \times c \times d$

With typical values ($v = 2$, $c = 10$, $d = 90$):

Total violations = 2 × 10 × 90 = 1,800 violations after 3 months

Cleanup cost scales with violations:

Time to fix 1 violation ≈ 2 minutes
Time to fix 1,800 violations ≈ 3,600 minutes ≈ 60 hours ≈ 7.5 days

Compare to early linting cost:

Setup time: 30 minutes (once)
Per-commit overhead: 5 seconds (automated)
Total cost over 3 months: 30 minutes + (900 commits × 5 seconds) = 105 minutes ≈ 2 hours

ROI: Spend 2 hours upfront to save 60 hours later = 30x return.

Real-World Example

Scenario: SaaS Startup

A startup builds an MVP over 3 months:

Without Early Linting:

Month 1: 
  - Team ships fast, no linting
  - Codebase: 15,000 lines
  - Violations: ~300

Month 2:
  - Codebase: 35,000 lines
  - Violations: ~700

Month 3:
  - Codebase: 50,000 lines
  - Violations: ~1,000
  - CEO asks: "Can we add linting?"
  - Team faces 1-2 week cleanup or ratcheting complexity

With Early Linting:

Day 0:
  - Senior dev spends 30 minutes setting up ESLint
  - Pre-commit hooks enforce standards

Month 1:
  - Codebase: 15,000 lines
  - Violations: 0

Month 2:
  - Codebase: 35,000 lines
  - Violations: 0

Month 3:
  - Codebase: 50,000 lines
  - Violations: 0
  - CEO asks: "Can we add more strict rules?"
  - Team updates config in 5 minutes, runs --fix, done

Outcome: The second team can evolve quality standards easily because they started clean.

Best Practices

1. Start Strict, Loosen Gradually

Begin with strict rules, disable specific ones if they cause friction:

{
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/strict"],
  "rules": {
    // Disable only if genuinely needed
    "@typescript-eslint/no-explicit-any": "off"  // After team discussion
  }
}

Rationale: Easier to remove rules than add them to legacy code.

2. Use --fix Aggressively

Most violations are auto-fixable:

# Fix all auto-fixable issues
npm run lint:fix

# Remaining violations are meaningful (logic issues, etc.)

3. Integrate with Editor

Developers see violations as they type:

// .vscode/settings.json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.validate": ["javascript", "typescript"]
}

4. Document Exceptions

If you must disable a rule, explain why:

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function handleLegacyAPI(data: any) {
  // Legacy API returns untyped data - refactor planned for Q3
}

5. Evolve Standards Over Time

Start simple, add rules as the team matures:

// Month 1: Basic rules
{
  "extends": ["eslint:recommended"]
}

// Month 3: Add TypeScript
{
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"]
}

// Month 6: Add complexity limits
{
  "rules": {
    "complexity": ["error", 10],
    "max-lines": ["error", 200]
  }
}

Common Objections

“Linting slows down development”

Reality: Initial setup takes 30 minutes. Automated fixing is instant. Manual fixes take seconds per violation.

The alternative—fixing 1,000 violations later—takes days.

“We’ll add it after MVP”

Reality: “After MVP” never comes. You’ll face:

  • Customer bugs to fix
  • Feature requests to ship
  • Scaling challenges

Technical debt becomes “too expensive to fix” and persists forever.

“Our team is experienced, we don’t need linting”

Reality: Even senior developers benefit from:

  • Consistent style across team
  • Automated formatting (no bikeshedding)
  • Catching subtle bugs (no-unused-vars, no-fallthrough, etc.)

Linting isn’t about skill—it’s about consistency and automation.

“We can use Prettier instead”

Reality: Prettier handles formatting. ESLint handles quality:

// Prettier fixes
- Indentation
- Semicolons
- Quotes

// ESLint catches
- Unused variables
- Missing return types
- Complexity violations
- Security issues (no-eval, etc.)

Use both:

npm install --save-dev prettier eslint-config-prettier

Integration with AI Coding

Why Early Linting Matters More with LLMs

LLMs generate code that’s syntactically valid but may violate project standards:

// LLM might generate
function getUser(id) {  // Missing types
  const user = db.query(id)
  return user
}

// Linter forces
function getUser(id: string): Promise<User> {
  const user = await db.query(id);
  return user;
}

Claude Code Hooks Example

Automate linting on every AI-generated change:

// .claude/hooks/post-write.json
{
  "command": "npx eslint {file} --fix",
  "description": "Auto-fix linting issues in AI-generated code"
}

Now Claude’s code is always compliant, without manual intervention.

Metrics for Success

Track Violations Over Time

# Add to CI
npm run lint -- --format json > lint-report.json

# Track trend
Week 1: 0 violations
Week 4: 0 violations
Week 12: 0 violations

Goal: Maintain zero violations indefinitely.

Measure Code Review Efficiency

Before linting:

  • Average PR comments: 12 (8 style, 4 logic)
  • Time to merge: 2 days

After linting:

  • Average PR comments: 4 (0 style, 4 logic)
  • Time to merge: 4 hours

Result: Reviews focus on substance, not syntax.

Track Rework Time

Without early linting:
  - Month 3: 40 hours cleanup
  - Month 6: 60 hours cleanup
  - Total: 100 hours

With early linting:
  - Month 3: 0 hours cleanup
  - Month 6: 0 hours cleanup
  - Total: 0 hours

Savings: 100 hours

Conclusion

Early linting is preventive medicine for codebases. The upfront cost is trivial compared to the compounding cost of technical debt.

Key Takeaways:

  1. Enable linting on Day 0 of every project
  2. Automate enforcement with hooks and CI/CD
  3. Use --fix aggressively to minimize manual work
  4. Integrate with editors for real-time feedback
  5. Evolve standards as the team matures

The tradeoff is clear: Spend 30 minutes now to save hundreds of hours later. Every project deserves this investment.

Related Concepts

  • Claude Code Hooks as Quality Gates: Automate linting in AI workflows
  • Quality Gates as Information Filters: How linting reduces entropy
  • Test-Based Regression Patching: Apply similar prevention to testing
  • Entropy in Code Generation: Mathematical foundation for quality gates

Mathematical Foundation

$$V(t) = v \times c \times t$$

Technical Debt Accumulation Formula

This formula models how linting violations accumulate over time without quality gates.

$V(t)$ = Total violations at time $t$ (measured in days)

$v$ = Average violations introduced per commit (typically 1-3)

$c$ = Commits per day (typically 5-15 for active projects)

$t$ = Time in days

In Plain English:

Technical debt grows linearly with time when violations-per-commit is constant.

Example with real numbers:

  • $v = 2$ violations per commit (no linting enforcement)
  • $c = 10$ commits per day (active development)
  • $t = 90$ days (3 months)

After 3 months:

$V(90) = 2 \times 10 \times 90 = 1,800$ violations

Cleanup cost:

  • 2 minutes per violation × 1,800 = 3,600 minutes = 60 hours of work

With early linting:

  • $v = 0$ (violations caught and fixed immediately)
  • $V(90) = 0 \times 10 \times 90 = 0$ violations
  • Cleanup cost: 0 hours

The Cost of Waiting:

If you wait to introduce linting, you pay the accumulated cleanup cost:

  • Wait 1 month: $V(30) = 600$ violations = 20 hours cleanup
  • Wait 3 months: $V(90) = 1,800$ violations = 60 hours cleanup
  • Wait 6 months: $V(180) = 3,600$ violations = 120 hours cleanup

The longer you wait, the more expensive it becomes. Early linting breaks this curve by keeping $v = 0$.

Related Concepts

References

Topics
AutomationBest PracticesCi CdCode QualityEntropyEslintLintingPreventionQuality GatesTechnical Debt

More Insights

Cover Image for Thought Leaders

Thought Leaders

People to follow for compound engineering, context engineering, and AI agent development.

James Phoenix
James Phoenix
Cover Image for Systems Thinking & Observability

Systems Thinking & Observability

Software should be treated as a measurable dynamical system, not as a collection of features.

James Phoenix
James Phoenix