error TS2454: Variable 'discount' is used before being assigned. is reported when a variable declared with a type but no initial value is read on a path where nothing has assigned it yet. JavaScript would read undefined, which the declared type does not allow.
In a .js file the error needs a type on the variable (/** @type {number} */ let discount;). Without one, the variable's type grows to include undefined and the same read is reported as TS18048 instead.
At a glance
| Code | TS2454 |
|---|---|
| Message | Variable '{0}' is used before being assigned. |
| TypeScript's category | error |
| Listed under | Null, undefined and unknown |
| Turned on by | strictNullChecks, strict |
| In .js files | Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file |
The message is TypeScript's own text from its diagnostic messages file (Apache-2.0); {0} is filled in by tsc for each error.
What it looks like
/**
* @param {number} price
* @param {boolean} member
*/
export function memberPrice(price, member) {
/** @type {number} */
let discount;
if (member) discount = 0.1;
return price * (1 - discount);error TS2454: Variable 'discount' is used before being assigned.
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} What causes it
- The variable is assigned in some branches (
if (member) discount = 0.1;) but not all, and read after them. strictNullChecks(part ofstrict) is on.
How to fix it
Start with a value, or assign on every branch
let discount = 0; covers the missing branch and lets the checker infer number, so the @type tag is no longer needed. When the value is chosen by a condition, a const with a conditional expression is shorter still.
/**
* @param {number} price
* @param {boolean} member
*/
export function memberPrice(price, member) {
let discount = 0;
if (member) discount = 0.1;
return price * (1 - discount);
}
/**
* @param {number} price
* @param {boolean} member
*/
export function memberPriceTernary(price, member) {
const discount = member ? 0.1 : 0;
return price * (1 - discount);
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Gotchas
- A variable assigned inside a callback (
items.forEach(() => { total = ... })) is not counted as assigned when the code after the call runs, because the checker cannot know the callback ran. - Using a
letorconstbefore its declaration line is a different error (TS2448, used before its declaration); JavaScript throws aReferenceErrorfor it at run time too.
Sources
- Narrowing: Control flow analysis · TypeScript handbook
- TSConfig Reference: strictNullChecks · TypeScript
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.