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:
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:
- LLM generates code
- TypeScript compiler checks type invariants
- If invariant violated → Type error → LLM regenerates
- 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:
- LLM generates code with classes
- ESLint detects invariant violation
- Error: “Classes not allowed, use factory pattern”
- 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:
- LLM generates
withdrawimplementation - Test runs and checks invariant
- If invariant violated (balance < 0) → Test fails → LLM regenerates
- 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:
- CLAUDE.md loaded into context
- LLM generates code following documented patterns
- If pattern violated → Human review catches it OR later tests fail
- 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
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:
- Invariants constrain the valid state space from infinite programs to correct programs
- Each invariant eliminates invalid states through set intersection
- Quality gates verify invariants automatically
- Multiple invariants compound to exponentially reduce errors
- 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
- Quality Gates as Information Filters: How gates verify invariants
- Entropy in Code Generation: How invariants reduce uncertainty
- Type-Driven Development: Using types as invariant specifications
- Custom ESLint Rules for Determinism: Enforcing structural invariants
- Test-Based Regression Patching: Building invariant verifiers incrementally
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
nullwhen 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
- Information Theory for Coding Agents – Mathematical foundations showing how invariants reduce entropy
- LLM as Recursive Function Generator – How invariants fit into the retrieve-generate-verify loop
- Entropy in Code Generation – Invariants reduce entropy by constraining valid states
- Quality Gates as Information Filters – Gates verify invariants automatically
- Making Invalid States Impossible – Prevention-based invariant enforcement
- Type-Driven Development – Types encode invariants at compile-time
- Boundary Enforcement with Layered Architecture – Structural invariants enforce layer boundaries
- DDD Bounded Contexts for LLMs – Architectural invariants define domain boundaries
- Sub-Agent Architecture – Role-specific invariants constrain agent behavior
- Custom ESLint Rules for Determinism – Enforce structural invariants via linting
- Test-Based Regression Patching – Build invariant verifiers incrementally
References
- Design by Contract – Eiffel – Programming methodology that makes invariants explicit through preconditions, postconditions, and class invariants
- Type Systems – Programming Language Theory – How type systems enforce invariants at compile-time
- Hoare Logic – Formal system for reasoning about program correctness using invariants

