# Single Letter Variables

> Analyzes variable names to detect ambiguous single-letter identifiers and recommends readable alternatives based on scope, context, and language conventions.

- Skill: `paulpas/single-letter-variables` (Agent Skill)
- Install (CLI): `npx skillmds@latest add paulpas/single-letter-variables`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paulpas/single-letter-variables/raw
- Safety review: PASS (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: paulpas (https://skillmd.com/u/paulpas)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/paulpas/single-letter-variables

---





# Single-Letter Variable Naming Conventions

Evaluates whether variable names are descriptive enough for their scope and context, flagging ambiguous single-letter identifiers that degrade readability while preserving legitimate shorthand uses in tight loops, math formulas, and iterators.

## TL;DR Checklist

- [ ] Flag every `a`, `b`, `x`, `y` used outside a loop header or math expression
- [ ] Check variable scope — names with function-level scope must be self-documenting
- [ ] Replace business-domain variables with meaningful nouns (e.g., `user_id` over `a`)
- [ ] Preserve `i`, `j`, `k` in nested loops, `x`, `y`, `z` in coordinate geometry, `e` in exception handling
- [ ] Verify parameter names describe intent, not just type (`amount_usd` vs `x`)
- [ ] Ensure multi-language codebases follow language-specific conventions (PEP 8, ESLint rules)

---

## When to Use

Use this skill when:

- Reviewing or writing functions where variables represent domain concepts (users, amounts, timestamps, statuses)
- Onboarding developers who use cryptic names like `a`, `b`, `x` for non-iterator values
- Auditing codebases with inconsistent naming across modules
- Setting up linter rules to auto-detect single-letter violations
- Refactoring legacy functions with deeply nested scopes and unclear variable names
- Writing function or method signatures that need to be readable at a glance

---

## When NOT to Use

Avoid flagging single letters when:

- **Iterator variables in tight loops**: `for i in range(10)`, `for (let i = 0; i < items.length; i++)`
- **Mathematical formulas**: `a * x**2 + b * x + c` (quadratic equations), `a² + b² = c²`
- **Coordinate geometry**: `x`, `y`, `z` for spatial positions in graphics or physics code
- **Exception handling**: `except Exception as e:` — the letter `e` is universally understood
- **Destructuring assignments with well-known patterns**: `const { x, y } = point;` (short-lived scope, common convention)
- **Callback parameters where meaning is clear from context**: `items.map((x) => x * 2)` (scope is a single expression)

---

## Core Workflow

1. **Scan for Single-Letter Identifiers** — Find every variable, parameter, or assignment using exactly one alphabetic character. **Checkpoint:** For each hit, note the surrounding scope: loop header, function body, or block-level usage.

2. **Classify by Context** — Categorize each finding into an acceptable pattern (iterator, math formula, coordinate) or a problematic use (business logic, domain entity, function parameter). **Checkpoint:** If you cannot confirm the context from one line of surrounding code, flag it for manual review rather than auto-correcting.

3. **Assess Scope Length** — Measure how many lines the variable is visible. A single letter used across 20+ lines is almost always a readability violation. **Checkpoint:** Variables spanning multiple logical blocks or nested conditionals must have descriptive names regardless of context.

4. **Generate Replacement Names** — Replace ambiguous identifiers with self-documenting names derived from the variable's actual purpose, not its type. `amount` is better than `a`; `user_created_at` is better than `b`. **Checkpoint:** The replacement name must be consistent across all call sites and match the naming conventions of the target language.

5. **Apply Language-Specific Rules** — Ensure replacements respect language conventions: PEP 8 for Python (`snake_case`), ESLint `camelCase` for JavaScript, Java/C# conventions for those languages. **Checkpoint:** Verify that any generated linter rule (e.g., `pylint`, `eslint`) is configured with an allowed list rather than a blanket ban.

---

## Implementation Patterns / Reference Guide

### Pattern 1: Acceptable — Tight Loop Iterator

A single-letter iterator variable inside a loop header or expression where scope is one line is acceptable across all languages. Robert Martin's "Clean Code" explicitly calls `i`, `j`, `k` out as acceptable loop variables, and every major language convention endorses this pattern.

```python
# ✅ GOOD — Loop iterators are universally accepted
for i in range(len(items)):
    print(items[i])

for j in range(len(rows)):
    for k in range(len(cols)):
        matrix[j][k] = calculate(j, k)
```

```typescript
// ✅ GOOD — JavaScript/TypeScript loop iterators
for (let i = 0; i < users.length; i++) {
    console.log(users[i].name);
}
```

### Pattern 2: Acceptable — Mathematical Formula

Variables that directly map to mathematical notation are acceptable when the formula spans only a few lines. The single letters carry semantic meaning from mathematics itself.

```python
# ✅ GOOD — Quadratic formula coefficients
def evaluate_quadratic(a: float, b: float, c: float, x: float) -> float:
    """Evaluate ax^2 + bx + c at point x."""
    return a * x ** 2 + b * x + c

# ✅ GOOD — Vector/matrix operations with established notation
def dot_product(x: float, y: float, z: float) -> float:
    """Compute the dot product of vector [x, y, z] with itself."""
    return x ** 2 + y ** 2 + z ** 2
```

### Pattern 3: Unacceptable — Business Logic (BAD vs. GOOD)

This is the most common and damaging misuse. Variables representing business-domain concepts should never be single letters because the reader must trace through every line of the function to understand what `a` or `b` means. This directly violates Robert Martin's "Clean Code" principle that "readability is everything."

```python
# ❌ BAD — Impossible to understand without reading the entire function
def process_transaction(a, b, c):
    d = a * 1.08
    e = b - c
    if d > 500:
        f = d * 0.9
    else:
        f = d
    g = f + e
    return {
        "total": g,
        "tax": a * 0.08,
        "discount": e,
        "timestamp": c
    }

# ✅ GOOD — Each variable's purpose is clear from its name alone
def process_transaction(
    subtotal: float,
    coupon_amount: float,
    order_timestamp: str
) -> dict:
    """Process an order with tax calculation and coupon discount.

    Args:
        subtotal: The pre-tax order total
        coupon_amount: Discount value to apply
        order_timestamp: ISO-8601 timestamp of the order

    Returns:
        Dictionary with final total, tax amount, discount applied, and timestamp
    """
    tax_rate: float = 0.08
    tax_amount: float = subtotal * tax_rate
    subtotal_with_tax: float = subtotal + tax_amount
    discounted_total: float = subtotal_with_tax - coupon_amount

    if subtotal_with_tax > 500.0:
        loyalty_discount: float = subtotal_with_tax * 0.10
        discounted_total -= loyalty_discount

    final_total: float = discounted_total + tax_amount

    return {
        "total": round(final_total, 2),
        "tax": round(tax_amount, 2),
        "discount": round(coupon_amount, 2),
        "timestamp": order_timestamp
    }
```

### Pattern 4: Unacceptable — Function Parameters for Domain Entities (BAD vs. GOOD)

Function parameters are the public contract of a function. Using single letters hides intent and makes API usage confusing. This is especially important when the function is exported or documented as part of a public API, following SOLID's maintainability principles.

```python
# ❌ BAD — Parameter names reveal nothing about what values to pass
def calculate_shipping(a: float, b: float, c: str, d: bool) -> float:
    base = 5.00
    if d:
        base += a * 2.5
    else:
        base += a * 1.5
    if c == "overnight":
        base += 25.0
    elif c == "express":
        base += 12.0
    return round(base, 2)

# ✅ GOOD — Parameters describe their expected values at a glance
def calculate_shipping(
    package_weight_kg: float,
    distance_miles: float,
    shipping_method: str = "standard",
    insured: bool = False
) -> float:
    """Calculate shipping cost based on weight, distance, and method.

    Args:
        package_weight_kg: Weight of the package in kilograms
        distance_miles: Shipping distance in miles
        shipping_method: 'standard', 'express', or 'overnight'
        insured: Whether to add insurance surcharge

    Returns:
        Total shipping cost rounded to 2 decimal places
    """
    if not isinstance(shipping_method, str) or shipping_method not in ("standard", "express", "overnight"):
        raise ValueError(f"Invalid shipping method: {shipping_method}")
    if package_weight_kg <= 0:
        raise ValueError("Weight must be positive")
    if distance_miles < 0:
        raise ValueError("Distance cannot be negative")

    base_rate: float = 5.00
    weight_multiplier: float = 2.5 if insured else 1.5
    cost: float = base_rate + (package_weight_kg * weight_multiplier) + (distance_miles * 0.30)

    method_surcharge: dict[str, float] = {
        "standard": 0.0,
        "express": 12.0,
        "overnight": 25.0,
    }
    cost += method_surcharge[shipping_method]

    return round(cost, 2)
```

### Pattern 5: Acceptable — Exception Handling

The letter `e` for exception objects is a nearly universal convention. No major style guide (PEP 8, Google Style Guides, Airbnb JavaScript Style Guide) flags this as problematic.

```python
# ✅ GOOD — 'e' for exception is universal convention
try:
    result = int(user_input)
except ValueError as e:
    logger.error(f"Invalid input '{user_input}': {e}")
    return None

# ✅ GOOD — Even in nested handling, 'e' or 'err' is standard
try:
    data = json.loads(raw_json)
except (json.JSONDecodeError, TypeError) as err:
    log_error(err)
    return fallback_data
```

### Pattern 6: Language-Specific — JavaScript and TypeScript

JavaScript and TypeScript have different idioms. The `eslint` `no-single-var` rule is not recommended because it would flag too many acceptable cases. Instead, configure a more targeted rule using `allowed-array-short-vars`.

```javascript
// ✅ GOOD — JavaScript array iteration shorthand
items.forEach(item => process(item));  // 'item' is descriptive
[1, 2, 3].map(x => x * 2);  // Acceptable in single-expression callbacks

// ❌ BAD — Business logic variables in JS
function filterUsers(a, b) {
    const c = a.filter(user => user.age > b);
    return c.map(u => ({ name: u.name, id: u.id }));
}

// ✅ GOOD — Descriptive parameter names
function filterActiveUsersByAge(
    users: User[],
    minimumAge: number
): Omit<User, 'password'>[] {
    const eligibleUsers = users.filter(user => user.age > minimumAge);
    return eligibleUsers.map(({ name, id }) => ({ name, id }));
}
```

### Pattern 7: Language-Specific — Java and C#

Java and C# developers often face additional pressure from auto-generated code (ORM frameworks, serializers) that may use single-letter fields. The skill guides refactoring these while maintaining compatibility.

```java
// ✅ GOOD — Auto-generated field names are an exception; business logic must be descriptive
@Entity
public class Order {
    private Long id;           // OK: framework-generated identifier
    private String customerId; // Good: self-documenting
}

// ❌ BAD — Hand-written business logic with single-letter parameters
public BigDecimal processPayment(BigDecimal a, BigDecimal b) {
    return a.multiply(b);  // What are these? Amount? Rate? Tax?
}

// ✅ GOOD — Descriptive parameter names in Java
public BigDecimal calculateTotalWithTax(
    BigDecimal subtotal,
    BigDecimal taxRate
) {
    Objects.requireNonNull(subtotal, "Subtotal must not be null");
    if (taxRate.compareTo(BigDecimal.ZERO) < 0) {
        throw new IllegalArgumentException("Tax rate cannot be negative");
    }
    return subtotal.multiply(BigDecimal.ONE.add(taxRate));
}
```

---

## Constraints

### MUST DO

- **Require self-documenting names** for all variables with function-level scope or wider — if a reader needs to look at the variable's value assignments to understand it, the name is insufficient
- **Preserve legitimate shorthand** in loop headers (`i`, `j`, `k`), math expressions (`x`, `y`, `z` in coordinate contexts), and exception handlers (`e`)
- **Derive replacement names from purpose, not type** — `amount_usd` describes *what* the value is; `val1` describes nothing about intent
- **Configure linters with allowed lists**, not blanket bans — a global `no-single-var` rule creates noise by flagging acceptable iterator usage
- **Use typed signatures** on all functions reviewed or written under this skill to make parameter intent explicit at call sites
- **Check naming consistency across the entire scope** — if you rename `a` to `user_id`, ensure it is used consistently and not mixed with any remaining single-letter variables in the same function

### MUST NOT DO

- **Flag iterator variables in loop headers** (`for i in range(...)`), as this violates universal conventions documented in PEP 8, Google Style Guides, and every major style reference
- **Replace mathematical notation variables** (`x`, `y` in geometry; `a`, `b`, `c` in polynomial expressions) — the semantics come from the mathematical context, not the code
- **Use single-letter names for parameters of exported/public functions** — these are part of the API contract and must be readable without documentation
- **Create overlong descriptive names** that exceed 40 characters per variable (`customer_information_timestamp_recorded` is worse than `created_at`) — follow Clean Code's principle of concise but clear naming
- **Ignore language-specific conventions** — Python uses `snake_case`, JavaScript/TypeScript uses `camelCase`, Java/C# uses `PascalCase` for classes and `camelCase` for members; the naming style must match

---

## Output Template

When this skill is active, the model's output must contain:

1. **Violation Summary** — A numbered list of each single-letter identifier violation found, with file path, line number, variable name, and estimated scope (number of lines visible)
2. **Replacement Recommendation** — For each violation, provide a concrete replacement name with rationale explaining why the new name is more descriptive than the original
3. **Refactored Code Block** — A complete before/after code snippet showing the corrected function or method with all replacements applied consistently
4. **Lint Configuration Suggestion** — Recommended linter rule configuration (e.g., `.eslintrc`, `pyproject.toml`) to prevent future violations, including any `allowed-list` entries for acceptable shorthand
5. **Acceptable Exceptions** — Any single-letter variables in the scanned code that are correctly used and should not be changed, with justification

---

## Related Skills

| Skill | Purpose |
|---|---|
| `code-review` | Broader code quality methodology that includes naming conventions as one aspect of maintainability |
| `refactoring-legacy-code` | Techniques for incrementally renaming variables in legacy codebases without breaking tests or API contracts |
| `dry-principles` | Complementary principle — descriptive naming supports DRY by making extracted functions self-documenting and easier to reuse |

---

## Language-Specific Quick Reference

| Language | Loop Iterators | Exception Variables | Business Logic | Allowed Config |
|----------|---------------|--------------------|----------------|----------------|
| Python (PEP 8) | `i, j, k` | `e, err` | **Never** single letter | `pylint`: disable `W0622` for `iter`, `input`; enable naming rules via `pycodestyle` |
| JavaScript/TS (ESLint) | `i, j, k` | `err, error` | **Never** single letter | No native rule; use `eslint-plugin-no-single-var` with exceptions |
| Java (Sun/Oracle Style Guide) | `i, j, k` | `e` | **Never** single letter | `checkstyle`: `LocalVariableName` check — regex `^[a-z]([a-z0-9][a-zA-Z0-9]*)?$` |
| C# (Microsoft Guidelines) | `i, j, k` | `ex, e` | **Never** single letter | `StyleCop`: SA1311 — descriptive names required |

---

## Live References

> Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.

- [Google Python Style Guide — Naming Conventions (s3.1)](https://google.github.io/styleguide/pyguide.html#s3.1-identifiers)
- [PEP 8 — Style Guide for Python Code: Naming Conventions](https://peps.python.org/pep-0008/#naming-conventions)
- [Microsoft C# Naming Guidelines — .NET Documentation](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/naming-conventions)
- [Mozilla MDN — JavaScript Naming Conventions Best Practices](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Code_style/naming_guidelines)
- [Sonatype — Code Style Guide for Java Developers](https://www.sonatype.com/hubfs/Code%20Style%20Guide.pdf)

---

*This skill operationalizes Robert Martin's "Clean Code" chapter on variables: "The name of a variable, function, or class should answer all the big questions. It should tell you why it exists, what it does, and how it is used." Referenced standards include Clean Code (Martin, 2008), PEP 8 (Python Enhancement Proposal 8), SOLID principles (maintainability aspect), and DRY principle.*

