@typescript-eslint/no-unused-vars: Underscore, Disable and Options

@typescript-eslint/no-unused-vars reports variables, parameters, imports and types that you declared and never used. The usual advice is to rename the thing with a leading underscore, and on a default setup that does not work:

function handler(req: string, res: string, _next: string) { ... }
//                                             ^ still an error

The underscore convention has to be switched on. This guide shows the configuration that enables it, along with the inline comments for one-off cases and the options that decide what gets checked in the first place.

Every block below was run on ESLint v10.11.0, TypeScript 6.0.3.

Why the underscore prefix does not work by default

Here is a file with an underscore-prefixed variable and an underscore-prefixed parameter, linted with nothing but the recommended config:

eslint.config.mjs

import tseslint from "typescript-eslint";

export default tseslint.config(
  ...tseslint.configs.recommended,
);

sample.ts

const used = 1;
const unusedVar = 2;
const _ignoredMaybe = 3;

function handler(req: string, res: string, _next: string) {
  return used;
}

try {
  handler("a", "b", "c");
} catch (err) {
  console.log("failed");
}

import type { Foo } from "./types";

Result:


C:\pyguides\runs\eslintvars\sample.ts
   2:7   error  'unusedVar' is assigned a value but never used      @typescript-eslint/no-unused-vars
   3:7   error  '_ignoredMaybe' is assigned a value but never used  @typescript-eslint/no-unused-vars
   5:18  error  'req' is defined but never used                     @typescript-eslint/no-unused-vars
   5:31  error  'res' is defined but never used                     @typescript-eslint/no-unused-vars
   5:44  error  '_next' is defined but never used                   @typescript-eslint/no-unused-vars
  11:10  error  'err' is defined but never used                     @typescript-eslint/no-unused-vars
  15:15  error  'Foo' is defined but never used                     @typescript-eslint/no-unused-vars

✖ 7 problems (7 errors, 0 warnings)
Terminal showing typescript-eslint no-unused-vars still reporting an underscore-prefixed parameter as an error
_ignoredMaybe and _next are reported like everything else.

Both underscore names are still errors. The recommended config sets no ignore pattern, so the underscore means nothing to the rule until you say it should.

Notice what else is caught: the unused catch binding and the unused import type. Both are checked out of the box.

The config that makes _name work

Add the rule with its options and the underscore convention starts working:

import tseslint from "typescript-eslint";

export default tseslint.config(
  ...tseslint.configs.recommended,
  {
    rules: {
      "@typescript-eslint/no-unused-vars": ["error", {
        args: "all",
        argsIgnorePattern: "^_",
        varsIgnorePattern: "^_",
        caughtErrors: "all",
        caughtErrorsIgnorePattern: "^_",
        destructuredArrayIgnorePattern: "^_",
        ignoreRestSiblings: true,
      }],
    },
  },
);
const used = 1;
const unusedVar = 2;
const _ignoredMaybe = 3;

function handler(req: string, res: string, _next: string) {
  return used;
}

try {
  handler("a", "b", "c");
} catch (err) {
  console.log("failed");
}

import type { Foo } from "./types";

Result:


C:\pyguides\runs\eslintvars\sample.ts
   2:7   error  'unusedVar' is assigned a value but never used. Allowed unused vars must match /^_/u  @typescript-eslint/no-unused-vars
   5:18  error  'req' is defined but never used. Allowed unused args must match /^_/u                 @typescript-eslint/no-unused-vars
   5:31  error  'res' is defined but never used. Allowed unused args must match /^_/u                 @typescript-eslint/no-unused-vars
  11:10  error  'err' is defined but never used. Allowed unused caught errors must match /^_/u        @typescript-eslint/no-unused-vars
  15:15  error  'Foo' is defined but never used. Allowed unused vars must match /^_/u                 @typescript-eslint/no-unused-vars

✖ 5 problems (5 errors, 0 warnings)
Terminal showing typescript-eslint no-unused-vars ignoring underscore-prefixed names once the ignore patterns are configured
With the patterns set, only the genuinely unnamed leftovers are reported.

The underscore names have gone, and the remaining errors now spell out the rule in their message: Allowed unused args must match /^_/u. That is a useful hint to leave in place for your team.

Each pattern covers a different position, which is why there are several of them rather than one global setting.

OptionCoversTypical value
varsIgnorePatternVariables, imports, types, interfaces, enums"^_"
argsIgnorePatternFunction parameters"^_"
caughtErrorsIgnorePatternThe binding in catch (e)"^_"
destructuredArrayIgnorePatternSkipped slots in array destructuring"^_"
ignoreRestSiblingsKeys removed by a rest spreadtrue

ignoreRestSiblings is the one people misread. It excuses the siblings you pulled out to drop them, not the ...rest variable itself, so an unused rest is still reported.

Disable no-unused-vars for one line or one file

When the fix is genuinely local, a comment beats changing the config:

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const draftForLater = 1;

/* eslint-disable @typescript-eslint/no-unused-vars */
const scratchA = 1;
const scratchB = 2;
/* eslint-enable @typescript-eslint/no-unused-vars */

const stillReported = 3;

Result:


C:\pyguides\runs\eslintvars\inline.ts
  9:7  error  'stillReported' is assigned a value but never used. Allowed unused vars must match /^_/u  @typescript-eslint/no-unused-vars

✖ 1 problem (1 error, 0 warnings)

eslint-disable-next-line covers the line that follows it. The block form switches the rule off until a matching eslint-enable, and /* eslint-disable @typescript-eslint/no-unused-vars */ at the very top of a file covers the whole file.

Naming the rule in the comment matters. A bare eslint-disable-next-line turns off every rule on that line, which hides problems you did want to hear about.

CommentScope
// eslint-disable-next-line @typescript-eslint/no-unused-varsThe next line only
// eslint-disable-line @typescript-eslint/no-unused-varsThe line it sits on
/* eslint-disable @typescript-eslint/no-unused-vars */From there to the end of the file, or to eslint-enable
"@typescript-eslint/no-unused-vars": "off"Everywhere the config block applies
"@typescript-eslint/no-unused-vars": "warn"Still reported, but stops failing the build

The args option: after-used versus all

The default does not report every unused parameter, which surprises people who expect a clean sweep:

With the default, args: "after-used":

export function tailUnused(a: number, b: number, unusedTail: number) {
  return a + b;
}

export function middleUnused(first: number, middle: number, last: number) {
  return first + last;
}

Result:


C:\pyguides\runs\eslintvars\args.ts
  1:50  error  'unusedTail' is defined but never used  @typescript-eslint/no-unused-vars

✖ 1 problem (1 error, 0 warnings)

middle is not reported. Under after-used, only parameters that come after the last one you actually used count, because removing a middle parameter would change the call signature.

With args: "all":

export function tailUnused(a: number, b: number, unusedTail: number) {
  return a + b;
}

export function middleUnused(first: number, middle: number, last: number) {
  return first + last;
}

Result:


C:\pyguides\runs\eslintvars\args.ts
  1:50  error  'unusedTail' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  5:45  error  'middle' is defined but never used. Allowed unused args must match /^_/u      @typescript-eslint/no-unused-vars

✖ 2 problems (2 errors, 0 warnings)

Now both are reported. Use all together with argsIgnorePattern so that anything deliberately unused is marked with an underscore, which documents the intent. That pairs well with how TypeScript functions declare their parameters.

Turn off the base no-unused-vars rule

ESLint ships its own no-unused-vars, and leaving it enabled alongside the TypeScript one reports everything twice:

import tseslint from "typescript-eslint";

export default tseslint.config(
  ...tseslint.configs.recommended,
  { rules: { "no-unused-vars": "error" } },   // the BASE rule, left on by mistake
);
const neverUsed = 1;
type Shape = { a: number };
interface Unused { b: string }
enum Colour { Red }

Result:


C:\pyguides\runs\eslintvars\types.ts
  1:7   error  'neverUsed' is assigned a value but never used  no-unused-vars
  1:7   error  'neverUsed' is assigned a value but never used  @typescript-eslint/no-unused-vars
  2:6   error  'Shape' is defined but never used               no-unused-vars
  2:6   error  'Shape' is defined but never used               @typescript-eslint/no-unused-vars
  3:11  error  'Unused' is defined but never used              no-unused-vars
  3:11  error  'Unused' is defined but never used              @typescript-eslint/no-unused-vars
  4:6   error  'Colour' is defined but never used              no-unused-vars
  4:6   error  'Colour' is defined but never used              @typescript-eslint/no-unused-vars
  4:15  error  'Red' is defined but never used                 no-unused-vars

✖ 9 problems (9 errors, 0 warnings)
Terminal showing every unused variable reported twice because the base ESLint no-unused-vars rule was left enabled
Nine problems where there are only four, because both rules are running.

Switching the base rule off leaves the TypeScript one to do the work:

import tseslint from "typescript-eslint";

export default tseslint.config(
  ...tseslint.configs.recommended,
  { rules: { "no-unused-vars": "off" } },
);
const neverUsed = 1;
type Shape = { a: number };
interface Unused { b: string }
enum Colour { Red }

Result:


C:\pyguides\runs\eslintvars\types.ts
  1:7   error  'neverUsed' is assigned a value but never used  @typescript-eslint/no-unused-vars
  2:6   error  'Shape' is defined but never used               @typescript-eslint/no-unused-vars
  3:11  error  'Unused' is defined but never used              @typescript-eslint/no-unused-vars
  4:6   error  'Colour' is defined but never used              @typescript-eslint/no-unused-vars

✖ 4 problems (4 errors, 0 warnings)

Four problems instead of nine, and the same ones. The TypeScript rule is a superset: it understands type aliases, interfaces, enums and type-only imports, which the base rule handles badly or not at all.

Most setups get this right automatically, because tseslint.configs.recommended disables the base rule for you. It only resurfaces when someone adds it back by hand. Unused enum members behave the same way, which matters when working with enum values.

Unused catch bindings

An unused error binding is reported by default, as the first example showed. There are three ways out, in order of preference:

  1. Drop the binding entirely: try { ... } catch { ... }, which TypeScript has allowed for years.
  2. Rename it _err and set caughtErrorsIgnorePattern: "^_".
  3. Set caughtErrors: "none" to stop checking them at all, which is the blunt option.

The first is usually right. If you are not inspecting the error, there is no reason to name it. Typing caught errors covers what to do when you are.

Unused imports, types and interfaces

The rule covers far more than local variables. Everything in this file is reported:

An unused import type, an unused type alias, an unused interface and an unused enum all count as unused variables, along with the enum’s members.

That is deliberate, because dead type declarations mislead readers exactly as dead code does. For imports specifically, most editors can strip them on save, which removes the problem before the linter sees it.

Which fix should you reach for?

  • Delete it. Most reports are genuinely dead code, and the rule has done its job.
  • Underscore plus ignore patterns for parameters you must keep for a signature, such as an Express handler’s _next.
  • Bare catch { } when the error is not inspected.
  • An inline disable comment for a genuine one-off, naming the rule explicitly.
  • "warn" instead of "error" while migrating a large codebase, so the build still passes.
  • "off" only as a last resort, since unused code is one of the cheapest bugs to catch.

More TypeScript guides worth reading next:

Frequently asked questions

Why is my underscore variable still flagged by no-unused-vars?

Because the underscore means nothing to the rule until you configure it. Add argsIgnorePattern: "^_" and varsIgnorePattern: "^_" to the rule options.

How do I disable @typescript-eslint/no-unused-vars for one line?

Put // eslint-disable-next-line @typescript-eslint/no-unused-vars on the line above. Always name the rule, or you disable every rule on that line.

How do I ignore unused function parameters in TypeScript?

Prefix them with an underscore and set argsIgnorePattern: "^_". Parameters you cannot remove because they sit before ones you use are the normal reason to do this.

What is the difference between no-unused-vars and @typescript-eslint/no-unused-vars?

The TypeScript version also understands type aliases, interfaces, enums and type-only imports. Run only one of them, or every problem is reported twice.

Why is my catch variable reported as unused?

Caught errors are checked by default. Use catch { } with no binding, rename it _err with caughtErrorsIgnorePattern, or set caughtErrors: "none".

What does args: after-used mean?

It is the default, and it only reports parameters that come after the last used one. A middle parameter is left alone because removing it would change the signature. args: "all" reports every one.

Should I turn the rule off completely?

Rarely. Setting it to "warn" keeps the signal without failing the build, which suits a migration. The full option list is in the typescript-eslint no-unused-vars reference.