LearnFrenzy
Sign in Take a free test

TypeScript Basic Types · Lesson 4 of 17

any, unknown, never, Literal Types and Enums

4 min read Updated Nov 8, 2026 Saurabh Samir

A few special types do a lot of work in real code. unknown safely represents "I don't know yet" (unlike the dangerous any), never marks impossible cases, and literal types let a variable hold only specific values like "pending" | "shipped". Knowing when to use each – and when to avoid enums – is a mark of an experienced TypeScript developer.

In this lesson

  • any vs unknown
  • void and never
  • Literal types and as const
  • Enums vs union types

any vs unknown

const fromApi: any = JSON.parse('{"name":"Asha"}');
console.log(fromApi.name.toUpperCase());        // compiles: any switches checking off
// fromApi.age.toFixed(2) would also compile – and crash at run time

const safe: unknown = JSON.parse('{"name":"Ravi","age":21}');
// safe.name;                                  // error: 'safe' is of type 'unknown'
if (typeof safe === "object" && safe !== null && "name" in safe && typeof safe.name === "string") {
  console.log(safe.name.toUpperCase());         // allowed after checking
}

Output

ASHA
RAVI
anyunknown
Accepts any valueyesyes
Lets you use it freelyyes – no checks at allno – you must narrow it first
Spreads to other codeyes, silentlyno
Use foremergencies while migrating old JSAPI responses, JSON, user input, catch errors

any turns TypeScript off

Every any is a hole in your type safety, and it spreads: values derived from it are any too. Prefer unknown and narrow it, and enable ESLint's no-explicit-any rule in team projects.

void and never

function logVisit(page: string): void {        // returns nothing useful
  console.log(`visited ${page}`);
}

function fail(message: string): never {        // never returns normally
  throw new Error(message);
}

function parsePercent(text: string): number {
  const n = Number(text);
  if (Number.isNaN(n) || n < 0 || n > 100) fail(`invalid percent: ${text}`);
  return n;                                    // TypeScript knows fail() didn't return
}

logVisit("/learning/typescript");
console.log(parsePercent("87"));
try {
  parsePercent("120");
} catch (e) {
  console.log(e instanceof Error ? e.message : e);
}

Output

visited /learning/typescript
87
invalid percent: 120

void means "returns nothing you should use". never means "this can never happen" – a function that always throws or loops forever, or a variable in a branch that's impossible. Chapter 5 uses never for exhaustiveness checks.

Literal types and as const

type Status = "pending" | "shipped" | "delivered";   // only these three strings
type Rating = 1 | 2 | 3 | 4 | 5;

let status: Status = "pending";
status = "shipped";
// status = "lost";                                 // error: not assignable to type 'Status'

function rate(stars: Rating): string { return "★".repeat(stars); }
console.log(status, rate(4));

const config = { env: "production", retries: 3 } as const;   // deeply readonly, literal types
// config.retries = 5;                             // error: retries is read-only
const roles = ["admin", "editor", "viewer"] as const;
type Role = (typeof roles)[number];                 // "admin" | "editor" | "viewer"
const r: Role = "editor";
console.log(config.env, roles.length, r);

Output

shipped ★★★★
production 3 editor

as const freezes the types of a literal: strings stay literal ("production", not string) and everything becomes readonly. Deriving Role from the roles array keeps the list and the type in sync automatically.

Enums vs union types

enum Direction { Up, Down, Left, Right }                 // numeric enum: 0, 1, 2, 3
enum LogLevel { Info = "INFO", Warn = "WARN", Error = "ERROR" }   // string enum

console.log(Direction.Left, Direction[2], LogLevel.Warn);

type Level = "INFO" | "WARN" | "ERROR";                  // the union-type alternative
function log(level: Level, msg: string): void { console.log(`[${level}] ${msg}`); }
log("WARN", "disk 90% full");
log(LogLevel.Error, "service down");                     // a string enum value is assignable

Output

2 Left WARN
[WARN] disk 90% full
[ERROR] service down
enumUnion of literals
Exists at run timeyes – generates a JavaScript objectno – erased completely
Works with type stripping (Node, esbuild isolated files)no – needs a real compileryes
Values in callsLogLevel.Warn"WARN"
Iterate over valuesyes (with care)use an as const array

Modern recommendation

Many teams now prefer union types (plus an as const array when they need the list at run time) over enums: they're simpler, have no runtime code and work with Node's built-in type stripping. Enums are still common in Angular codebases – know both.

Practice

Try it yourself

  1. Define const SIZES = ["S", "M", "L", "XL"] as const, derive a Size type, and write isSize(value: unknown): value is Size.

    Show solution
    const SIZES = ["S", "M", "L", "XL"] as const;
    type Size = (typeof SIZES)[number];
    
    function isSize(value: unknown): value is Size {
      return typeof value === "string" && (SIZES as readonly string[]).includes(value);
    }
    for (const v of ["M", "XXL", 42]) console.log(v, isSize(v));

    Output

    M true
    XXL false
    42 false

Quick check

Why prefer unknown over any?

unknown forces you to check the value before using it; any disables all checking.

What does a function returning never do?

It never returns normally – it always throws or never ends.

What does as const do to an object literal?

Makes it deeply readonly and keeps literal types for its values.

Summary

  • Avoid any; use unknown for untrusted data and narrow it.
  • void = no useful return; never = impossible or never returns.
  • Literal unions and as const model fixed sets of values; prefer them to enums in modern code.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links