What's Meant by Type Space and Value Space in TypeScript?

In TypeScript, "type space" and "value space" are two concepts which can be described like so:

  1. Type space is where types are declared for the purpose of type checking JavaScript code at compile-time. However, these types are not available at runtime as they are erased during compilation. Types can be created using the type, interface, class and enum keywords;
  2. Value space contains values, such as literal values, variables, constants, parameters, functions, class declarations, and enums, which persist at runtime.

With the exception of classes and enums, values and types do not exist in the same namespace, which is why you cannot use a value where a type is expected:

const foo = '';

// Error: 'foo' refers to a value, but is being used as a type here.
type Test = keyof foo;

Similarly, except for classes and enums, you cannot use types as values (as they are removed at compile-time):

type Foo = string;

const foo: any = 'bar';

// Error: 'Foo' only refers to a type, but is being used as a value here.
if (foo instanceof Foo) {
    // ...
}

And for the same reasons, it is perfectly valid for a variable and type name to be the same (except for classes and enums):

type Foo = string;

const Foo = 'bar';

Class declarations and enums are unique in that they exist in both the type and the value space. This means that:

  • Classes or enums can be used as a type or a value;
  • Classes and enums persist after compilation;
  • Name of a class declaration or enum occupies the same name in both spaces and cannot be used as a variable or type with the same name.

For example, if you have the same name for a class and a variable, TypeScript will throw an error:

// Duplicate identifier 'Foo'.
class Foo {}

// Duplicate identifier 'Foo'.
const Foo = '';

However, the following does not throw an error since variables in JavaScript are case-sensitive:

class Foo {}

let foo: Foo; // valid

Since enums are compiled into JavaScript objects at runtime, they too have both, type and value representations. During the compilation process, the TypeScript compiler converts the TypeScript enum declaration to an equivalent JavaScript object. The resulting JavaScript object has the same properties and values as the original TypeScript enum.

For example, consider the following TypeScript code:

enum Color {
  Red,
  Green,
  Blue
}

It is compiled into the following JavaScript code at runtime:

var Color;
(function (Color) {
  Color[Color["Red"] = 0] = "Red";
  Color[Color["Green"] = 1] = "Green";
  Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));

Similar to classes, enums in type space and value space cannot have the same name:

// Error: Enum declarations can only merge with namespace or other enum declarations.
enum Color {
  Red,
  Green,
  Blue
}

// Error: Enum declarations can only merge with namespace or other enum declarations.
const Color = 'red';

However, enums with the same name can be declared in the type space, in which case they're merged:

enum Color {
  Red,
  Green,
  Blue
}

enum Color {
  Yellow = 3
}

const color: Color = Color.Yellow; // valid

The distinction between type space and value space is important because it allows TypeScript to use types to validate the correctness of your code, and to catch type mismatches before your code is executed. By using types to describe the structure and constraints of your values, TypeScript can help you write more correct and maintainable code.


This post was published by Daniyal Hamid. Daniyal currently works as the Head of Engineering in Germany and has 20+ years of experience in software engineering, design and marketing. Please show your love and support by sharing this post.