@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)

_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)

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.
| Option | Covers | Typical value |
|---|---|---|
varsIgnorePattern | Variables, imports, types, interfaces, enums | "^_" |
argsIgnorePattern | Function parameters | "^_" |
caughtErrorsIgnorePattern | The binding in catch (e) | "^_" |
destructuredArrayIgnorePattern | Skipped slots in array destructuring | "^_" |
ignoreRestSiblings | Keys removed by a rest spread | true |
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.
| Comment | Scope |
|---|---|
// eslint-disable-next-line @typescript-eslint/no-unused-vars | The next line only |
// eslint-disable-line @typescript-eslint/no-unused-vars | The 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)

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:
- Drop the binding entirely:
try { ... } catch { ... }, which TypeScript has allowed for years. - Rename it
_errand setcaughtErrorsIgnorePattern: "^_". - Set
caughtErrors: "none"to stop c