Invariants in Programming and LLM Code Generation

James Phoenix
James Phoenix

Summary

Invariants are properties that must always hold true during program execution. They form the foundation of quality gates in LLM code generation by constraining the valid state space. Type systems, tests, and linting rules all encode invariants that guide LLMs toward correct implementations and prevent entire classes of bugs.

What Are Invariants?

In programming, an invariant is a condition or property that must always be true at certain points during program execution. Invariants define the “rules of the game”—constraints that your code must satisfy to be considered correct.

Simple Examples

State Invariant: “A counter is never negative”

class Counter {
  private count: number = 0; // Invariant: count >= 0
  
  increment(): void {
    this.count++; // Maintains invariant (positive stays positive)
  }
  
  decrement(): void {
    // Maintains invariant by preventing negative values
    if (this.count > 0) {
      this.count--;
    }
  }
}

Type Invariant: “This variable is always a string”

let username: string = "alice"; // Invariant: username is always string
username = "bob"; // ✅ Maintains invariant
username = 123; // ❌ Violates invariant (compiler error)

Structural Invariant: “A binary search tree is always sorted”

class BSTNode {
  // Invariant: left.value < this.value < right.value
  value: number;
  left?: BSTNode;
  right?: BSTNode;
  
  insert(newValue: number): void {
    // Maintains BST invariant by choosing correct subtree
    if (newValue < this.value) {
      // Goes left
    } else {
      // Goes right
    }
  }
}

Business Invariant: “Total equals sum of line items”

class Invoice {
  items: LineItem[];
  total: number;
  
  // Invariant: total === sum(items.map(i => i.price))
  addItem(item: LineItem): void {
    this.items.push(item);
    this.total += item.price; // Maintains invariant
  }
}

Invariants in Programming Language Theory

Invariants are fundamental to how programming languages ensure correctness:

1. Type Systems Enforce Invariants

Type systems are invariant enforcement mechanisms built into compilers:

// Type invariant: "x is always a number"
let x: number = 42;

// Compiler prevents invariant violations:
x = "hello"; // ❌ Type error: string is not assignable to number

The compiler statically verifies that type invariants hold before your code even runs.

Why this matters: Type errors catch invariant violations at compile-time, not runtime.

2. Contracts Define Invariants

Design by Contract (Eiffel, Ada) makes invariants explicit:

class BankAccount {
  private balance: number;
  
  // Invariant (contract)
  invariant(): boolean {
    return this.balance >= 0; // Balance never negative
  }
  
  // Precondition: amount > 0
  // Postcondition: balance increased by amount
  deposit(amount: number): void {
    assert(amount > 0, "Precondition: amount must be positive");
    this.balance += amount;
    assert(this.invariant(), "Invariant violated");
  }
  
  // Precondition: amount > 0 && amount <= balance
  // Postcondition: balance decreased by amount
  withdraw(amount: number): void {
    assert(amount > 0 && amount <= this.balance, "Precondition failed");
    this.balance -= amount;
    assert(this.invariant(), "Invariant violated");
  }
}

3. Compiler Optimizations Rely on Invariants

Compilers use invariants to optimize code safely:

void process(int *arr, size_t len) {
  // Invariant: arr is not null
  // Invariant: len is the actual array length
  
  for (size_t i = 0; i < len; i++) {
    // Compiler can optimize because invariants guarantee safety
    arr[i] *= 2;
  }
}

If the compiler knows arr is never null (invariant), it can skip null checks in the loop.

How Invariants Relate to LLM Code Generation

When an LLM generates code, it’s navigating a vast space of possible programs. Invariants constrain this space, guiding the LLM toward correct implementations.

Invariants as Constraints

Think of invariants as walls that block invalid code paths:

All Possible Programs (infinite)
    |
    | Type Invariants (TypeScript)
    v
Type-Safe Programs (millions)
    |
    | Structural Invariants (ESLint)
    v
Type-Safe, Well-Structured Programs (thousands)
    |
    | Behavioral Invariants (Tests)
    v
Type-Safe, Well-Structured, Correct Programs (tens)

Each layer of invariants eliminates invalid states, narrowing the space until only correct implementations remain.

The Mathematical View

Without invariants:

  • Valid code space: Very large (high entropy)
  • Many possible implementations (most incorrect)
  • LLM picks randomly from this huge space

With invariants:

  • Valid code space: Small (low entropy)
  • Few possible implementations (most correct)
  • LLM constrained to pick from correct subset

Formula:

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
Valid Code Space = All Programs ∩ I₁ ∩ I₂ ∩ ... ∩ Iₙ

Where Iᵢ = Programs satisfying invariant i

Each invariant intersects the previous set, shrinking the valid space:

|All Programs| =|Type-Safe Programs| = |All||Type Invariants| = Very Large
|Linted Programs| = |Type-Safe||Lint Invariants| = Large
|Tested Programs| = |Linted||Test Invariants| = Small

Categories of Invariants for LLM Code Generation

1. Type Invariants (Enforced by TypeScript)

Type invariants specify what type each expression must have:

// Type invariant: getUserById returns Promise<User | null>
function getUserById(id: string): Promise<User | null> {
  // LLM MUST return Promise<User | null>
  // Cannot return: Promise<User>, User, null, string, etc.
}

How the LLM experiences this:

  1. LLM generates code
  2. TypeScript compiler checks type invariants
  3. If invariant violated → Type error → LLM regenerates
  4. If invariant satisfied → Code accepted

Effect: LLM learns that type invariants are hard constraints that must be satisfied.

2. Structural Invariants (Enforced by ESLint)

Structural invariants specify how code is organized:

// Structural invariant: "No classes, only factory functions"
// ESLint rule: no-class

// ❌ Violates invariant
class UserService {
  getUser() { ... }
}

// ✅ Satisfies invariant
export const createUserService = (deps) => {
  return {
    getUser: () => { ... }
  };
};

How the LLM experiences this:

  1. LLM generates code with classes
  2. ESLint detects invariant violation
  3. Error: “Classes not allowed, use factory pattern”
  4. LLM regenerates with factory function

Effect: LLM learns project-specific architectural invariants.

3. State Invariants (Enforced by Tests)

State invariants specify what must be true about program state:

describe('BankAccount', () => {
  it('maintains invariant: balance never negative', () => {
    const account = new BankAccount(100);
    
    account.withdraw(50);
    expect(account.balance).toBe(50); // ✅ Positive
    
    account.withdraw(60); // Try to overdraw
    expect(account.balance).toBe(50); // ✅ Still positive (withdrawal rejected)
  });
});

How the LLM experiences this:

  1. LLM generates withdraw implementation
  2. Test runs and checks invariant
  3. If invariant violated (balance < 0) → Test fails → LLM regenerates
  4. If invariant holds → Test passes → Code accepted

Effect: LLM learns behavioral invariants from test assertions.

4. Architectural Invariants (Documented in CLAUDE.md)

Architectural invariants specify design rules and patterns:

# CLAUDE.md

## Architectural Invariants

1. **Dependency Inversion**: Domain layer NEVER imports infrastructure
   - ✅ Domain defines interfaces, infrastructure implements
   - ❌ Domain imports database adapters directly

2. **Factory Pattern**: All services use factory functions
   - ✅ export const createUserService = (deps) => { ... }
   - ❌ export class UserService { ... }

3. **Result Type**: Functions return Result<T, E>, never throw
   - ✅ return { success: true, data: user };
   - ❌ throw new Error('User not found');

How the LLM experiences this:

  1. CLAUDE.md loaded into context
  2. LLM generates code following documented patterns
  3. If pattern violated → Human review catches it OR later tests fail
  4. Pattern reinforced through examples and documentation

Effect: LLM learns high-level architectural invariants from documentation.

Practical Examples

Example 1: Type Invariants Prevent Runtime Errors

Without type invariants:

// No types = No invariants enforced
function processUser(user) {
  return user.email.toLowerCase(); // What if user.email is undefined?
}

processUser({ name: "Alice" }); // 💥 Runtime error: Cannot read property 'toLowerCase' of undefined

With type invariants:

interface User {
  email: string; // Invariant: email is always present and is string
}

function processUser(user: User): string {
  return user.email.toLowerCase(); // ✅ Safe! Invariant guarantees email exists
}

processUser({ name: "Alice" }); // ❌ Compile error: Property 'email' is missing

For LLMs: Type invariants prevent the LLM from generating code that violates type safety.

Example 2: Structural Invariants Enforce Patterns

Without structural invariants:

// No linting rules = No pattern enforcement
// Different developers use different patterns

// Developer A uses classes
class UserService {
  getUser() { ... }
}

// Developer B uses factory functions  
const createUserService = () => ({ getUser: () => { ... } });

// Developer C uses plain objects
const userService = { getUser: () => { ... } };

// Result: Inconsistent codebase, hard for LLM to learn patterns

With structural invariants (custom ESLint rule):

// ESLint rule: "no-classes-for-services"
// Invariant: All services MUST use factory pattern

// ❌ Violates invariant
class UserService { ... }
// ESLint error: Services must use factory pattern

// ✅ Satisfies invariant
export const createUserService = (deps: ServiceDeps) => {
  return {
    getUser: async (id: string) => { ... }
  };
};

For LLMs: Structural invariants force the LLM to use consistent patterns across the codebase.

Example 3: State Invariants Prevent Logic Bugs

Without state invariants:

class ShoppingCart {
  items: Item[] = [];
  total: number = 0;
  
  addItem(item: Item): void {
    this.items.push(item);
    // 🐛 Forgot to update total!
  }
  
  checkout(): void {
    chargeCard(this.total); // Charges $0 even if items in cart!
  }
}

With state invariants (enforced by tests):

describe('ShoppingCart', () => {
  it('maintains invariant: total equals sum of item prices', () => {
    const cart = new ShoppingCart();
    
    cart.addItem({ name: 'Book', price: 10 });
    expect(cart.total).toBe(10); // Invariant check
    
    cart.addItem({ name: 'Pen', price: 5 });
    expect(cart.total).toBe(15); // Invariant check
  });
});

Implementation that satisfies invariant:

class ShoppingCart {
  items: Item[] = [];
  total: number = 0;
  
  // Invariant: total === sum(items.map(i => i.price))
  addItem(item: Item): void {
    this.items.push(item);
    this.total += item.price; // ✅ Maintains invariant
  }
  
  checkout(): void {
    chargeCard(this.total); // ✅ Correct total
  }
}

For LLMs: State invariants (verified by tests) catch logic bugs that type systems miss.

Example 4: Architectural Invariants Enforce Boundaries

Without architectural invariants:

// Domain layer directly imports infrastructure
import { supabase } from '../../infrastructure/database/client';

export class User {
  async save(): Promise<void> {
    // 🐛 Domain layer coupled to Supabase
    await supabase.from('users').insert(this);
  }
}

// Problem: Can't switch databases without changing domain logic

With architectural invariants:

// CLAUDE.md invariant: "Domain never imports infrastructure"

// Domain layer (pure business logic)
export interface UserRepository {
  save(user: User): Promise<void>;
  findById(id: string): Promise<User | null>;
}

export class User {
  // No database logic here!
}

// Infrastructure layer (adapter)
import { supabase } from './client';
import { UserRepository, User } from '../../domain/user';

export class SupabaseUserRepository implements UserRepository {
  async save(user: User): Promise<void> {
    await supabase.from('users').insert(user);
  }
  
  async findById(id: string): Promise<User | null> {
    const { data } = await supabase.from('users').select('*').eq('id', id).single();
    return data ? new User(data) : null;
  }
}

For LLMs: Architectural invariants guide the LLM to follow layered architecture and dependency inversion.

Information Theory Perspective

Invariants reduce the state space of valid programs, which mathematically reduces entropy.

State Space Without Invariants

All syntactically valid programs = S_initial
|S_initial| = Very large (millions of programs)

Problem: LLM picks from this huge space, most programs are incorrect.

State Space With Invariants

Valid programs = S_initial ∩ I_type ∩ I_lint ∩ I_test ∩ I_arch

Where:
- I_type = Programs satisfying type invariants
- I_lint = Programs satisfying structural invariants
- I_test = Programs satisfying behavioral invariants
- I_arch = Programs satisfying architectural invariants

|Valid programs| = Small (tens of programs)

Benefit: LLM picks from tiny space, most programs are correct.

Each Invariant is a Set Intersection

Visually:

         All Programs (∞)
               |
               | Invariant 1: Types
               v
    ┌──────────────────────┐
      Type-Safe Programs    (millions)
    └──────────┬───────────┘
               | Invariant 2: Linting
               v
       ┌──────────────┐
           Linted      (thousands)
       └──────┬───────┘
              | Invariant 3: Tests
              v
         ┌─────────┐
          Tested    (hundreds)
         └────┬────┘
              | Invariant 4: Architecture
              v
           ┌──────┐
           │Valid   (tens)
           └──────┘

Each invariant filters out invalid programs, reducing the state space exponentially.

Invariants as Quality Gates

Every quality gate in AI-assisted coding is an invariant checker:

Quality Gate = Invariant Verifier

interface QualityGate {
  check(code: string): InvariantCheckResult;
}

type InvariantCheckResult =
  | { valid: true }
  | { valid: false; violations: string[] };

Examples of Quality Gates as Invariant Checkers

1. TypeScript Compiler:

const typeChecker: QualityGate = {
  check(code: string): InvariantCheckResult {
    // Verifies type invariants
    const diagnostics = ts.compile(code);
    if (diagnostics.length > 0) {
      return {
        valid: false,
        violations: diagnostics.map(d => `Type error: ${d.messageText}`)
      };
    }
    return { valid: true };
  }
};

2. ESLint:

const linter: QualityGate = {
  check(code: string): InvariantCheckResult {
    // Verifies structural invariants
    const results = eslint.lintText(code);
    if (results.errorCount > 0) {
      return {
        valid: false,
        violations: results.messages.map(m => `Lint error: ${m.message}`)
      };
    }
    return { valid: true };
  }
};

3. Test Suite:

const testRunner: QualityGate = {
  check(code: string): InvariantCheckResult {
    // Verifies behavioral invariants
    const results = vitest.run();
    if (results.numFailedTests > 0) {
      return {
        valid: false,
        violations: results.failedTests.map(t => `Test failed: ${t.name}`)
      };
    }
    return { valid: true };
  }
};

Stacking Quality Gates = Intersecting Invariants

const qualityPipeline: QualityGate[] = [
  typeChecker,  // Invariant 1: Type safety
  linter,       // Invariant 2: Structural patterns
  testRunner,   // Invariant 3: Behavioral correctness
];

function verifyCode(code: string): InvariantCheckResult {
  for (const gate of qualityPipeline) {
    const result = gate.check(code);
    if (!result.valid) {
      return result; // Invariant violated, reject code
    }
  }
  return { valid: true }; // All invariants satisfied
}

Result: Code is only accepted if it satisfies all invariants.

Best Practices

1. Make Invariants Explicit

❌ Implicit (hard for LLM to learn):

// Assume balance is never negative (not enforced)
function withdraw(amount: number): void {
  this.balance -= amount;
}

✅ Explicit (clear invariant):

// Invariant: balance >= 0 (enforced by code)
function withdraw(amount: number): boolean {
  if (amount > this.balance) {
    return false; // Reject to maintain invariant
  }
  this.balance -= amount;
  return true;
}

2. Encode Invariants in Types

❌ Weak invariants:

function processAge(age: number): void {
  // Assumes age is positive, but not enforced
  if (age < 0) throw new Error('Invalid age');
}

✅ Strong invariants:

type PositiveNumber = number & { readonly __brand: 'PositiveNumber' };

function makePositive(n: number): PositiveNumber | null {
  return n > 0 ? (n as PositiveNumber) : null;
}

function processAge(age: PositiveNumber): void {
  // Type system guarantees age is positive!
  // No runtime check needed
}

3. Verify Invariants with Tests

❌ No invariant verification:

test('addItem works', () => {
  cart.addItem({ price: 10 });
  expect(cart.items.length).toBe(1); // Only checks items added
});

✅ Invariant verification:

test('addItem maintains total invariant', () => {
  const cart = new ShoppingCart();
  
  cart.addItem({ price: 10 });
  expect(cart.total).toBe(10); // ✅ Checks invariant
  
  cart.addItem({ price: 5 });
  expect(cart.total).toBe(15); // ✅ Checks invariant
  
  // Invariant: total === sum(items.map(i => i.price))
  const expectedTotal = cart.items.reduce((sum, item) => sum + item.price, 0);
  expect(cart.total).toBe(expectedTotal); // ✅ Explicit invariant check
});

4. Document Architectural Invariants

❌ Undocumented patterns:

// LLM has no guidance, generates inconsistent code

✅ Documented invariants:

# CLAUDE.md

## Architectural Invariants

### Dependency Inversion (MUST)
- Domain layer defines interfaces
- Infrastructure layer implements interfaces
- Domain NEVER imports infrastructure

### Factory Pattern (MUST)  
- All services use factory functions
- NO classes for services
- Example: `export const createUserService = (deps) => { ... }`

### Result Type (MUST)
- Functions return `Result<T, E>`
- NEVER throw exceptions in business logic
- Example: `return { success: true, data: user }`

5. Use Custom Linting Rules for Project-Specific Invariants

❌ Relying on manual review:

// Hope developers remember to use factory pattern

✅ Automated enforcement:

// .eslintrc.js
module.exports = {
  rules: {
    // Custom rule: Enforce factory pattern invariant
    'no-service-classes': 'error',
    
    // Custom rule: Enforce domain/infrastructure boundary
    'no-infrastructure-in-domain': 'error',
    
    // Custom rule: Enforce Result type usage
    'use-result-type': 'error',
  }
};

Common Invariants in LLM Code Generation

Type Invariants

// "Function always returns specific type"
function getUser(id: string): Promise<User | null>

// "Property always exists on object"
interface User {
  email: string; // Never undefined
}

// "Array elements always have specific type"
const users: User[]; // Never contains non-User elements

State Invariants

// "Value stays within bounds"
class Percentage {
  // Invariant: 0 <= value <= 100
  constructor(private value: number) {
    if (value < 0 || value > 100) {
      throw new Error('Percentage must be between 0 and 100');
    }
  }
}

// "Relationship between fields"
class Rectangle {
  // Invariant: area === width * height
  constructor(
    public width: number,
    public height: number,
    public area: number
  ) {
    if (area !== width * height) {
      throw new Error('Area must equal width * height');
    }
  }
}

Structural Invariants

// "All services use factory pattern"
// ❌ class UserService { ... }
// ✅ const createUserService = () => { ... }

// "DTOs always imported from central location"
// ❌ import { UserDTO } from './local-types'
// ✅ import { UserDTO } from '@repo/types'

// "No default exports"
// ❌ export default UserService
// ✅ export const createUserService = ...

Architectural Invariants

// "Domain never imports infrastructure"
// ❌ import { supabase } from '../../infrastructure/db'
// ✅ import { UserRepository } from './interfaces'

// "Presentation never imports domain directly"
// ❌ import { User } from '../../domain/user'
// ✅ import { UserDTO } from './dto'

// "All async functions handle errors"
// ❌ async function getUser() { await fetch(...); }
// ✅ async function getUser(): Promise<Result<User, Error>> { ... }

Integration with Other Patterns

Invariants + Quality Gates

Quality gates verify invariants:

  • TypeScript → Verifies type invariants
  • ESLint → Verifies structural invariants
  • Tests → Verifies behavioral invariants
  • CI/CD → Verifies integration invariants

See: Quality Gates as Information Filters

Invariants + Entropy Reduction

Each invariant reduces entropy by eliminating invalid states:

  • High entropy = Many possible implementations (most wrong)
  • Low entropy = Few possible implementations (most correct)
  • Invariants reduce entropy by constraining valid code space

See: Entropy in Code Generation

Invariants + Type-Driven Development

Types encode invariants at compile-time:

  • Write types first (specification)
  • Implement to satisfy type invariants
  • Compiler verifies invariants automatically

See: Type-Driven Development

Invariants + Test-Based Regression Patching

Tests document invariants that were violated:

  • Bug occurs → Invariant was not enforced
  • Test added → Invariant now verified
  • Fix applied → Code satisfies invariant
  • Future → Test prevents invariant violation

See: Test-Based Regression Patching

Conclusion

Invariants are the foundation of reliable LLM code generation:

Key Insights:

  1. Invariants constrain the valid state space from infinite programs to correct programs
  2. Each invariant eliminates invalid states through set intersection
  3. Quality gates verify invariants automatically
  4. Multiple invariants compound to exponentially reduce errors
  5. Explicit invariants guide LLMs toward correct implementations

Practical Applications:

  • Type systems: Encode invariants at compile-time
  • Tests: Verify invariants at runtime
  • Linting rules: Enforce structural invariants
  • CLAUDE.md: Document architectural invariants
  • Custom rules: Enforce project-specific invariants

The Result: LLM-generated code that satisfies all invariants is provably correct within the defined constraints.

By understanding invariants, you can design better quality gates, write more informative tests, and create documentation that guides LLMs toward correct implementations.

Related Concepts

Mathematical Foundation

$$S_{\text{valid}} = S_{\text{initial}} \cap I_1 \cap I_2 \cap \cdots \cap I_n$$

Understanding the Invariant Intersection Formula

The formula S_valid = S_initial ∩ I₁ ∩ I₂ ∩ … ∩ Iₙ shows how invariants progressively constrain the valid code space.

S_valid – Valid Programs

S_valid is the set of programs that satisfy all invariants. This is what we’re calculating—the final set of correct implementations.

S_initial – All Possible Programs

S_initial is the starting set—all syntactically valid programs. This is huge (millions or infinite).

Example: All valid TypeScript functions that could be written.

– Intersection Symbol

The symbol means “intersection” or “AND”. It represents taking only elements that exist in both sets.

Think of it as a filter:

Set A = {1, 2, 3, 4, 5}
Set B = {3, 4, 5, 6, 7}
A ∩ B = {3, 4, 5} (only elements in both)

Iᵢ – Programs Satisfying Invariant i

I₁, I₂, …, Iₙ are sets of programs that satisfy specific invariants:

  • I₁: Programs satisfying invariant 1 (e.g., type-safe programs)
  • I₂: Programs satisfying invariant 2 (e.g., linted programs)
  • I₃: Programs satisfying invariant 3 (e.g., tested programs)
  • Iₙ: Programs satisfying invariant n

Putting It Together

The formula says:

“Valid programs are those that satisfy ALL invariants simultaneously”

Step by step:

Step 1: Start with all possible programs
S = S_initial (millions of programs)

Step 2: Filter by first invariant (types)
S = S_initial ∩ I₁ (only type-safe programs remain)

Step 3: Filter by second invariant (linting)
S = (S_initial ∩ I₁) ∩ I₂ (only type-safe AND linted programs)

Step 4: Filter by third invariant (tests)
S = ((S_initial ∩ I₁) ∩ I₂) ∩ I₃ (only type-safe AND linted AND tested programs)

...

Final: All invariants applied
S_valid = S_initial ∩ I₁ ∩ I₂ ∩ ... ∩ Iₙ (only programs satisfying ALL invariants)

Concrete Example

Let’s say we’re implementing a getUserById function.

S_initial: All possible TypeScript functions

  • Could return anything: void, User, Promise<User>, string, null, etc.
  • Could be sync or async
  • Could throw exceptions or return errors
  • Millions of possibilities

I₁ (Type Invariant): Must return Promise<User | null>

  • async function getUserById(id: string): Promise<User | null>
  • function getUserById(id: string): User (not async)
  • async function getUserById(id: string): Promise<User> (doesn’t handle null)
  • Reduces possibilities to ~thousands

I₂ (Structural Invariant): Must use repository pattern

  • const user = await userRepository.findById(id);
  • const user = await supabase.from('users').select('*') (direct DB access)
  • Reduces possibilities to ~hundreds

I₃ (Behavioral Invariant): Must pass tests

  • ✅ Returns null when user not found (test expects this)
  • ❌ Throws error when user not found (test fails)
  • Reduces possibilities to ~tens

S_valid: Only implementations satisfying all three invariants

// One of the few valid implementations:
async function getUserById(id: string): Promise<User | null> {
  // Satisfies I₂: Uses repository pattern
  const user = await userRepository.findById(id);
  
  // Satisfies I₃: Returns null (not throws) when not found
  return user;
  
  // Satisfies I₁: Return type is Promise<User | null>
}

Visual Representation

┌─────────────────────────────────────┐
│     S_initial (All Programs)        │  ← Huge set
│         (millions)                  │
└─────────────────┬───────────────────┘
                  │ ∩ I₁ (Type Invariant)
                  v
        ┌─────────────────────┐
        │  Type-Safe Programs │  ← Smaller
        │     (thousands)     │
        └──────────┬──────────┘
                   │ ∩ I₂ (Structural Invariant)
                   v
              ┌────────────┐
              │   Linted   │  ← Smaller still
              │ (hundreds) │
              └─────┬──────┘
                    │ ∩ I₃ (Behavioral Invariant)
                    v
                ┌────────┐
                │ Tested │  ← Very small
                │ (tens) │
                └────────┘
                    ║
                    ║ This is S_valid
                    v

Each intersection shrinks the set, eliminating invalid programs.

Key Insight

Intersection is multiplicative, not additive:

If I₁ eliminates 90% of programs: 1,000,000  100,000
If I₂ eliminates 90% of remaining: 100,000  10,000
If I₃ eliminates 90% of remaining: 10,000  1,000

Total reduction: 99.9% (not 270%!)

Each invariant filters the remaining set, creating exponential improvement.

Why This Matters for LLMs

The LLM is sampling from the final set S_valid:

  • Without invariants: Sampling from S_initial (mostly wrong)
  • With invariants: Sampling from S_valid (mostly correct)

The more invariants you add (larger n), the smaller S_valid becomes, and the higher the probability the LLM generates correct code.

Related Concepts

References

Topics
Code GenerationCompiler TheoryConstraintsFoundationsInvariantsLlm MechanicsQuality GatesState SpaceType SafetyType Systems

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