TypeScript ESLint: Why Your Config Is Probably Wrong (2026)

Well, that Tuesday debugging session was a real headache, wasn’t it? The culprit? A single, unawaited promise in a background job that silently failed, causing a cascading memory leak that took down our staging cluster. Embarrassing, to say the least.

The worst part wasn’t the downtime. It was realizing that a simple ESLint rule could have caught it three weeks ago when I wrote the code. We tend to treat linters like glorified spellcheckers — tools to nag us about semicolons or indentation — when they should be our safety net against stupidity.

And if you’re writing TypeScript in 2026 without type-aware linting enabled, you’re basically driving a Ferrari with the handbrake on. You have the engine, but you aren’t using the power. Here’s how I actually set up TypeScript ESLint to catch the bugs that AI assistants and tired developers (myself included) keep introducing.

The “Flat Config” Reality Check

Remember the collective groan when ESLint forced the migration to eslint.config.js? Yeah, I hated it too. But now that I’ve converted my fourth monorepo, I have to admit: it’s better. Not easier, just better.

The biggest mistake I see? People copy-pasting the “recommended” config and calling it a day. The recommended config is weak. It’s designed not to annoy beginners. But you aren’t a beginner.

Here’s the setup I’m currently running on a Node 24.2 backend service. It uses typescript-eslint‘s strict type-checked rules.

TypeScript code on monitor - Learn how to document JavaScript/TypeScript code using JSDoc ...
TypeScript code on monitor – Learn how to document JavaScript/TypeScript code using JSDoc …
// eslint.config.mjs
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
  eslint.configs.recommended,
  ...tseslint.configs.strictTypeChecked,
  ...tseslint.configs.stylisticTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        projectService: true, // The game changer for performance
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      // My non-negotiables
      '@typescript-eslint/no-floating-promises': 'error',
      '@typescript-eslint/await-thenable': 'error',
      '@typescript-eslint/no-explicit-any': 'warn',
      'no-console': ['warn', { allow: ['warn', 'error'] }],
    },
  }
);

Catching the “AI Hallucination” Bugs

We all use AI to scaffold code now. It’s faster. But AI is confident and wrong. It loves to invent APIs or assume types that don’t match reality. This is where type-aware linting saves your bacon.

1. The Async Trap

Here’s a classic scenario. You ask an AI to write a function that updates a user profile and logs an event. It gives you this:

async function updateUser(userId: string, data: UserData) {
  const user = await db.users.update(userId, data);
  
  // ❌ BAD: This promise is floating!
  // If analytics fails, nobody knows. If it throws, unhandled rejection.
  analytics.track('user_update', { userId }); 
  
  return user;
}

The fix isn’t always to await it (you might not want to block). The fix is to be explicit:

async function updateUser(userId: string, data: UserData) {
  const user = await db.users.update(userId, data);
  
  // ✅ GOOD: Explicitly handle the background task
  void analytics.track('user_update', { userId }).catch((err) => {
    logger.error('Analytics failed', err);
  });
  
  return user;
}

DOM Manipulation: Where Null Goes to Die

Don’t do this:

TypeScript code on monitor - How to Make a VS Code Extension Using TypeScript: A Step-by-Step ...
TypeScript code on monitor – How to Make a VS Code Extension Using TypeScript: A Step-by-Step …
function setupModal() {
  // ❌ The '!' tells TypeScript "trust me", but runtime doesn't care about trust
  const modal = document.getElementById('user-modal')!;
  const input = modal.querySelector('input')!;
  
  input.value = 'default';
}

The linter forces you to write defensive code that actually survives in the wild:

function setupModal() {
  const modal = document.getElementById('user-modal');
  if (!modal) return; // Fail gracefully
  
  const input = modal.querySelector('input');
  // Optional chaining is your friend
  if (input) {
    input.value = 'default';
  }
}

API Responses: Trust Nothing

Another area where I see people get lazy is typing API responses. It’s tempting to slap any on a fetch result just to get the red squigglies to go away. But any leaks. It spreads through your codebase like a virus, disabling type checking everywhere it touches.

interface ApiResponse {
  id: number;
  title: string;
}

async function fetchData(url: string): Promise<ApiResponse> {
  const response = await fetch(url);
  
  // ✅ Better: Type assertion (still risky, but explicit)
  const data = (await response.json()) as unknown;
  
  if (!isValidResponse(data)) {
    throw new Error('Invalid schema');
  }
  
  return data;
}

// Simple type guard
function isValidResponse(data: unknown): data is ApiResponse {
  return (
    typeof data === 'object' &&
    data !== null &&
    'id' in data &&
    'title' in data
  );
}

Performance: The Elephant in the Room

TypeScript code on monitor - TypeScript 6.0: What's New and Improved | Ali N. posted on the ...
TypeScript code on monitor – TypeScript 6.0: What’s New and Improved | Ali N. posted on the …

Here’s the trick I used to survive the revolt:

  1. Split the config. We run standard (fast) linting on git commit hooks. It catches syntax errors and style issues instantly.
  2. Run heavy linting in CI only. The type-aware rules run in the pipeline. If you break a type rule, you find out 5 minutes later, not 5 seconds later. It’s a trade-off, but it keeps the local dev loop fast.
  3. Use tsc --noEmit first. Often, running the TypeScript compiler directly is faster than running ESLint with type information. I usually run tsc first to catch actual type errors, then let ESLint catch the logic bugs.

Final Thoughts

Tools like TypeScript ESLint aren’t there to make your code “pretty.” They’re there to compensate for the fact that JavaScript is inherently chaotic and our brains (and our AI assistants) are prone to shortcuts. I was skeptical about the strict configs initially. But after avoiding three potential production fires in Q4 2025 solely because the linter flagged a “misused promise” or an “unsafe return,” I’m converted. Turn on the strict rules. Fix the errors. Your future self, debugging at 2 AM on a Tuesday, will thank you.

If you’re looking to improve your TypeScript testing strategy, check out our article on TypeScript Tests That Actually Catch Bugs (And Don’t Make You Cry).

Mateo Rojas

Learn More →

Leave a Reply

Your email address will not be published. Required fields are marked *