TypeScript Keywords Reference

JavaScript has its own set of reserved words (let, function, class, and so on), but TypeScript layers a second vocabulary on top of that: words which only exist to describe types, not runtime behavior. Some of these, like interface and type, only exist at compile time and vanish completely once your code is compiled to JavaScript. Others, like enum and namespace, actually generate real runtime code. Knowing which is which — and what each keyword actually does — is essential to reading and writing TypeScript fluently. This lesson is a single reference page for every TypeScript-specific keyword, grouped by purpose.

Overview: two categories of keywords

Every TypeScript keyword falls into one of two buckets, and the distinction matters a lot in practice.

Type-space keywords exist purely for the type checker. The compiler reads them, uses them to verify your code, and then erases them entirely — the emitted JavaScript never mentions them. interface, type, keyof, typeof (in a type position), infer, is, as, satisfies, readonly (on type members), and declare all fall here. If you compiled a file that used nothing but these, and stripped out any runtime statements, you would get an empty (or near-empty) JavaScript file.

Value-space keywords (in TypeScript’s extended sense) produce actual JavaScript at runtime, but TypeScript adds extra rules or extra syntax around them. enum compiles to a real object. namespace compiles to an IIFE-wrapped object. abstract, implements, public/private/protected, static, override, and constructor-parameter readonly all modify classes, which are themselves real JavaScript constructs.

Understanding which bucket a keyword is in tells you immediately whether it affects your bundle size and runtime behavior, or whether it’s a compile-time-only annotation that disappears without a trace.

Syntax and quick reference table

The table below is the core reference. Skim it once, then use the worked examples to see the less obvious ones (infer, satisfies, in as a mapped-type modifier) in context.

Keyword Space Purpose
type Type Declares a named type alias (object shape, union, tuple, mapped type, etc.)
interface Type Declares an object shape; supports declaration merging and extends
enum Value Declares a named set of numeric or string constants; compiles to a real object
keyof Type Produces a union of a type’s property names as string/number literals
typeof Type (dual) In a type position, extracts the static type of a value (variable, const, function)
in Type Iterates a union of keys inside a mapped type ([K in Keys])
infer Type Introduces a type variable to be inferred inside a conditional type
is Type Marks a function as a custom type guard (value is string)
as Type Type assertion (override the checker) or rename in import ... as
satisfies Type Validates a value against a type without widening or losing its literal type
asserts Type Marks a function as an assertion function that narrows its parameter or throws
readonly Type/Value Marks a property or array/tuple as immutable to the type checker
declare Type Declares an ambient value that exists elsewhere, with no emitted code
namespace Value Groups related code under a single named object, compiled to real JS
abstract Value Marks a class or member that must be implemented by a subclass
implements Value Declares that a class satisfies an interface’s shape
public / private / protected Value Class member visibility, enforced only at compile time
static Value Declares a class member that belongs to the class itself, not instances
override Value Asserts a method intentionally overrides a base class method
never / unknown / any Type Special built-in types for "impossible", "unchecked-but-safe", and "opt out of checking"

Examples

Example 1: interface, enum, readonly, and keyof together

This example shows the everyday keywords: an interface for shape, a readonly field that can’t be reassigned, an enum for a fixed set of states, and keyof to derive the list of valid property names from the interface itself (so the two never drift apart).

interface User {
  readonly id: number;
  name: string;
  role?: "admin" | "member";
}

enum Status {
  Active,
  Inactive,
}

type UserKeys = keyof User; // "id" | "name" | "role"

function describe(user: User): string {
  return `${user.name} is ${Status[Status.Active]}`;
}

const alice: User = { id: 1, name: "Alice", role: "admin" };
console.log(describe(alice));

const keys: UserKeys[] = ["id", "name", "role"];
console.log(keys);

Output:

Alice is Active
[ 'id', 'name', 'role' ]

Note that Status[Status.Active] works because a numeric enum generates a reverse mapping at runtime — Status.Active is 0, and Status[0] gives back the string "Active". Also note id is readonly: if you tried alice.id = 2 anywhere, tsc would report "Cannot assign to ‘id’ because it is a read-only property" — but nothing stops you from mutating it in plain JavaScript, since readonly is erased at compile time.

Example 2: satisfies, typeof, in, is, and infer

This example is denser and shows the more advanced type-space keywords working together: satisfies checks a literal against a shape while keeping its precise type, typeof pulls a type out of a value, a user-defined type guard uses is, and a conditional type uses infer to pull the element type out of an array type.

const config = {
  host: "localhost",
  port: 8080,
} satisfies Record;

type Config = typeof config;

function isString(value: unknown): value is string {
  return typeof value === "string";
}

type ElementType = T extends (infer U)[] ? U : never;
type Item = ElementType; // resolves to number

function logHost(cfg: Config) {
  if ("host" in cfg) {
    console.log(`Host: ${cfg.host}`);
  }
}

logHost(config);

const value: unknown = "hello";
if (isString(value)) {
  console.log(value.toUpperCase());
}

const port = config.port as number;
console.log(`Port: ${port}`);

Output:

Host: localhost
HELLO
Port: 8080

The key detail is satisfies: it checks that config is assignable to Record<string, string | number> without actually changing config‘s inferred type to that wider type. Because of this, typeof config still knows port is specifically number (not the union), which is why config.port as number is a safe, redundant-but-legal assertion here. Had we written const config: Record<string, string | number> = {...} instead, we’d have widened the type and lost that precision.

Example 3: declare, namespace, abstract, and override

This example shows the class-related keywords, plus declare for an ambient value that’s assumed to exist without TypeScript emitting anything for it.

declare const VERSION: string;

namespace Shapes {
  export abstract class Shape {
    abstract area(): number;

    describe(): string {
      return `Area: ${this.area().toFixed(2)}`;
    }
  }

  export class Circle extends Shape {
    private radius: number;

    constructor(radius: number) {
      super();
      this.radius = radius;
    }

    override area(): number {
      return Math.PI * this.radius ** 2;
    }
  }
}

const circle = new Shapes.Circle(2);
console.log(circle.describe());

Output:

Area: 12.57

declare const VERSION tells the checker "trust me, this exists somewhere (perhaps injected by a build tool)" and emits nothing — it’s purely a promise to the compiler. abstract class Shape can’t be instantiated directly (new Shape() would be a compile error), and abstract area() forces every subclass to provide an implementation. override doesn’t change runtime behavior at all; it just tells the checker to verify that a same-named method really exists on the base class, catching typos when a base class method gets renamed.

Under the hood: what survives compilation

If you compile all three examples above and look at the output JavaScript, you’ll see the pattern clearly. The interface User, type UserKeys, type Config, type ElementType, and every : SomeType annotation disappear completely — there is zero trace of them in the emitted JS. keyof, typeof (in type position), infer, is, satisfies, and declare are compile-time-only instructions to the type checker; none of them produce a single byte of output.

By contrast, enum Status compiles into an actual object with both forward (Active: 0) and reverse (0: "Active") mappings built at runtime. namespace Shapes compiles into an immediately-invoked function that builds an object with Shape and Circle attached to it — Shapes.Circle is a real property access at runtime, not a type-only path. The class modifiers abstract, private, protected, and override are checked only while compiling; the emitted class has ordinary methods and properties, because JavaScript itself (pre-#private-fields) has no concept of member visibility enforced by the runtime.

Common Mistakes

Mistake 1: using as to force an incompatible type. A type assertion doesn’t convert a value — it just tells the compiler to trust you, which can produce runtime bugs the checker would otherwise have caught.

const value: unknown = "42";
const n = value as number; // compiles, but 'n' is really still a string
console.log(n + 1); // "421", not 43

This compiles without error (which is exactly the danger), because as only asserts — it performs no conversion and no runtime check. Use Number(value) or a validated type guard instead of asserting across unrelated types.

Mistake 2: forgetting that declare creates no runtime value.

declare const API_KEY: string;
console.log(API_KEY.length);

This type-checks fine — tsc trusts the declaration — but if nothing in your actual build ever defines a global API_KEY, running the compiled JavaScript throws ReferenceError: API_KEY is not defined. declare is a promise to the compiler, not a guarantee about the runtime environment; you must ensure the value really is injected (via a script tag, bundler define, or global assignment) before use.

Best Practices

  • Prefer interface for object shapes that might be extended or merged, and type for unions, tuples, and mapped types — interface can’t express a union directly.
  • Reach for satisfies instead of an explicit type annotation whenever you want validation without losing literal/narrow types.
  • Avoid casting with as across unrelated types; if you need runtime confidence, write an is-based type guard or an asserts function instead.
  • Use keyof typeof to derive literal unions from existing runtime objects (like config maps) instead of hand-maintaining a parallel type.
  • Avoid namespace in modern module-based code — it predates ES modules; prefer regular import/export unless you’re authoring global ambient type declarations.
  • Add override to every subclass method that’s meant to replace a base class method, so renames in the base class surface as compiler errors instead of silent bugs.

Practice Exercises

  • Exercise 1: Write an interface Product with id, name, and price, then use keyof to create a type ProductKey and a function getField(product: Product, key: ProductKey) that returns the corresponding value.
  • Exercise 2: Write a custom type guard function isNumberArray(value: unknown): value is number[] and use it to safely sum an array only when the guard passes.
  • Exercise 3: Declare a const settings = { theme: "dark", retries: 3 } satisfies Record<string, string | number>, then write a function that logs settings.theme in uppercase — confirm that typeof settings still reports theme as the literal type string rather than a wider union.

Summary

  • TypeScript keywords split into type-space (erased at compile time: type, interface, keyof, infer, is, satisfies, declare) and value-space (produce real JS: enum, namespace, class modifiers).
  • keyof and typeof let you derive types from existing types or values, keeping them in sync automatically.
  • satisfies validates a value’s shape without widening its inferred type — prefer it over an explicit annotation when you want both.
  • as is an assertion, not a conversion — it can silently hide real bugs if misused.
  • declare promises the compiler a value exists elsewhere; it emits nothing, so the runtime environment must actually provide it.
  • abstract, override, and visibility modifiers (public/private/protected) are compile-time-only guarantees on top of ordinary JavaScript classes.