Fix TypeScript 'Enum Is Not Assignable' Error: Type 'Status.Active' is not assignable to type 'Status.Inactive'

intermediate๐Ÿ”ต TypeScript2026-03-23| TypeScript 4.x / 5.x, any OS (Windows, macOS, Linux), any editor with TypeScript support

Error Message

Type 'Status.Active' is not assignable to type 'Status.Inactive'. ts(2322)
#typescript#enum#comparison#type

The Error

This one shows up when you try to pass or assign enum members as if they're interchangeable:

enum Status {
  Active,
  Inactive,
  Pending,
}

function setStatus(s: Status.Inactive) {
  // ...
}

const current = Status.Active;
setStatus(current); // โŒ Type 'Status.Active' is not assignable to type 'Status.Inactive'. ts(2322)

Same error when assigning between typed variables:

let a: Status.Active = Status.Active;
let b: Status.Inactive = a; // โŒ ts(2322)

Root Cause

TypeScript treats each enum member as its own literal type โ€” not just a value, but an actual distinct type. So Status.Active and Status.Inactive are as different as true and false at the type level.

At runtime? They're just numbers (0 and 1). But the type checker doesn't care. A variable typed as Status.Inactive will only accept Status.Inactive โ€” full stop.

Numeric enums have one more quirk worth knowing: they accept plain number literals (so let s: Status = 0 compiles). But enum members still aren't assignable to each other when their types differ.

Fix 1: Widen the Type to the Full Enum

Nine times out of ten, this is the fix. If the function should accept any Status value, type the parameter as Status, not Status.Inactive:

// โŒ Before โ€” accidentally too narrow
function setStatus(s: Status.Inactive) { ... }

// โœ… After โ€” accepts any Status value
function setStatus(s: Status) { ... }

const current = Status.Active;
setStatus(current); // OK

The over-narrow type usually creeps in via autocomplete โ€” you type Status., pick Inactive, and TypeScript locks in the literal type before you realize it.

Fix 2: Use a Union of Specific Members

Need to accept some members but not all? A union type makes the intent explicit:

function handleResolved(s: Status.Active | Status.Inactive) {
  console.log(s);
}

handleResolved(Status.Active);   // โœ…
handleResolved(Status.Inactive); // โœ…
handleResolved(Status.Pending);  // โŒ Correctly rejected

Fix 3: Use Type Guards Before Assigning

When you already have a broad Status value but need to pass it somewhere that expects a specific member, use a condition first. TypeScript's control flow analysis will do the rest:

function requireInactive(s: Status.Inactive) {
  console.log('Inactive:', s);
}

function process(s: Status) {
  if (s === Status.Inactive) {
    requireInactive(s); // โœ… TypeScript narrows s to Status.Inactive here
  }
}

Inside that if block, TypeScript knows s can only be Status.Inactive. The assignment becomes safe without any casting.

Fix 4: Type Assertion (Last Resort)

Only reach for this when you're certain the runtime value is correct but TypeScript can't prove it โ€” for example, after fetching a value from an external API:

const s = getStatusFromAPI() as Status.Inactive;
requireInactive(s); // โœ… โ€” but you're bypassing type safety

If you find yourself writing as SomeEnum.Member in more than one or two places, the type design needs rethinking โ€” not more assertions.

Fix 5: Drop the Enum, Use String Literal Unions

A lot of modern TypeScript codebases skip enums entirely. String literal unions are simpler, produce clearer errors, and avoid the whole literal-type confusion:

// Instead of:
enum Status {
  Active = 'active',
  Inactive = 'inactive',
  Pending = 'pending',
}

// Use:
type Status = 'active' | 'inactive' | 'pending';

function setStatus(s: Status) { ... }

setStatus('active');   // โœ…
setStatus('inactive'); // โœ…
setStatus('unknown');  // โŒ Correctly rejected

No enum quirks, no runtime object, no inlined values to worry about. Just strings with compiler-enforced constraints.

Fix 6: const Enum Has the Same Problem

const enum inlines values at compile time, but the type rules are identical. Same fix applies โ€” widen the type or use a union:

const enum Direction {
  Up,
  Down,
}

// โŒ Too narrow
let d: Direction.Up = Direction.Down; // ts(2322)

// โœ… Widened
let d: Direction = Direction.Down; // OK

Verification

Once you've applied a fix, confirm it's actually resolved:

  • The red squiggly under the assignment should disappear immediately in your editor.
  • Run a full project type-check to catch anything the editor might have missed:
npx tsc --noEmit
# No output = no errors

  • If you used a type guard (Fix 3), add a quick test that exercises both the matching and non-matching branches โ€” confirm the guard behaves correctly at runtime, not just at compile time.

Quick Reference

  • Parameter too narrow โ†’ change type from Status.X to Status
  • Need subset of members โ†’ use Status.X | Status.Y
  • Narrowing inside a condition โ†’ use if (s === Status.X) before passing
  • Avoiding enum complexity altogether โ†’ switch to string literal unions

Prevention

The root cause is almost always the same: a variable or parameter accidentally typed as a specific enum member (Status.Active) when the intent was the full enum (Status). Autocomplete is the usual culprit โ€” you fill in an initial value and TypeScript locks in the literal type.

Three habits that prevent this:

  • Write explicit type annotations for enum variables: let s: Status = Status.Active rather than letting TypeScript infer Status.Active from the right-hand side.
  • Prefer string enums or string literal unions. The error messages are more readable and there are far fewer surprising edge cases.
  • Turn on strict mode in tsconfig.json. It surfaces these mismatches at the point of definition, before they ripple through three layers of function calls and become confusing.

Related Error Notes