@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 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:
- Catch error types in TypeScript
- Functions in TypeScript
- Arrays of objects in TypeScript
- Avoid duplicate enum values
- Add functions to interfaces
- TypeScript interview questions
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.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn