---
title: 'Basic Types'
source: 'https://academia.sh/en/courses/typescript/basic-types'
course: TypeScript
language: en
updated: '2026-08-17T18:09:54+00:00'
license: 'CC BY-SA 4.0'
---

# Basic Types

The first typed version of the measurement record, using primitive types, array and readonly array notations, tuples, enums, and object types.

The previous lesson established what the type layer does and does not do. Next comes
this layer's vocabulary: to write the measurement record's fields, the types available
first have to be known.

TypeScript's type vocabulary sits on top of JavaScript's value model. It adds no new
value kind; it names the kinds that already exist and gives ways to build compound
types from them. This lesson introduces primitive types, arrays, tuples, and enums,
and closes by writing the measurement record's first typed version.

## Primitive Types

Every primitive value introduced in the JavaScript Fundamentals course has a type name.
Type names are written in lowercase:

```typescript
const id: string = "s-01";
const value: number = 21.4;
const valid: boolean = true;
const description: string | null = null;
const bigCounter: bigint = 9007199254740993n;

console.log(typeof id, typeof value, typeof valid, typeof description, typeof bigCounter);
console.log(0.1 + 0.2 === 0.3);
console.log(Number.isInteger(value), Number.isSafeInteger(9007199254740993));
```

Output:

```text
string number boolean object bigint
false
false false
```

Three observations are needed.

**`number` is a single number type.** There is no split between integer and real
number; both are held in the IEEE 754 double-precision representation. The limits
established in the Floating Point Numbers lesson of the How Computers Work course hold
here exactly as they are — the `false` on the second line is a direct consequence.
The type system does not remove this limit, it only guarantees that a number is a
number.

**`typeof null` is `"object"`.** This is an old inconsistency the language carries;
the type system recognizes a separate type for `null` but does not change the runtime
behavior. It is an example showing that types are erased: the `null` type the compiler
sees and the value `typeof` sees come from the same place but do not say the same
thing.

**`bigint` and `symbol` are separate types.** `bigint` is used where the safe-integer
limit of the number type is exceeded, `symbol` for unique property keys. Neither can be
confused with `number`; `bigint` and `number` cannot be added directly.

`null` and `undefined` are two separate types and, under the default strict
configuration, cannot be assigned to other types on their own. This is why `description`
is declared above as `string | null`; had it been declared as just `string`, the
compiler would reject the assignment. This behavior depends on the `strictNullChecks`
option and will be covered in detail in the Compiler Configuration lesson.

## Arrays and Readonly Arrays

An array's type is written by appending `[]` to the element type. An equivalent
notation also exists:

```typescript
const values: number[] = [21.4, 22.1, 23.0];
const ids: Array<string> = ["s-01", "s-02"];
const readonlyValues: readonly number[] = [21.4, 22.1];

console.log(values.length, ids.length, readonlyValues.length);
readonlyValues.push(23.0);
```

The compiler produces this diagnostic for the last line:

```text
b2.ts(6,16): error TS2339: Property 'push' does not exist on type 'readonly number[]'.
```

`number[]` and `Array<number>` are the same type; the first is the short notation.
`readonly number[]` is a different type: an array whose elements can be read but not
changed. Mutating methods (`push`, `pop`, `sort`, `splice`) are not defined on this
type, so calling them produces a "no such property" diagnostic.

The `readonly` here is the type-level counterpart of the **immutable** concept from
the Programming Fundamentals course — but only at the type level. Nothing called
`readonly` survives in compiled output; the array is an ordinary array at runtime, and
code that has not passed type checking can mutate it. Real immutability requires
runtime tools like `Object.freeze`.

## Tuples

A **tuple** is an array whose element count and each position's type are fixed. It
suits representing the measurement record's valid value range:

```typescript
type Interval = [low: number, high: number];

const validInterval: Interval = [-40, 85];
const [low, high] = validInterval;
console.log(low, high);

const invalid: Interval = [-40, 85, 120];
```

The diagnostic for the last line:

```text
b3.ts(7,7): error TS2322: Type '[number, number, number]' is not assignable to type 'Interval'.
  Source has 3 element(s) but target allows only 2.
```

The names `low:` and `high:` document the tuple's elements; they have no runtime
counterpart, and the array is still read by position. Their value is readability:
`[number, number]` does not say which number comes first, `[low: number, high:
number]` does.

The distinction between a tuple and an array is this: an array does not know **how
many** elements there are, it knows they are all the **same** type; a tuple knows how
many elements there are and knows the type of **each one separately**.

## Fixed Value Sets: `enum`

The measurement record's `unit` field can only take certain values. One way to name
such a set is an **enum**:

```typescript
enum Unit {
  Celsius = "C",
  Pascal = "Pa",
  Percent = "%",
}

const unit: Unit = Unit.Celsius;
console.log(unit, Unit.Pascal);
```

When this file is compiled with `tsc --target es2022 --strict unit.ts`, the JavaScript
produced is:

```javascript
"use strict";
var Unit;
(function (Unit) {
    Unit["Celsius"] = "C";
    Unit["Pascal"] = "Pa";
    Unit["Percent"] = "%";
})(Unit || (Unit = {}));
const unit = Unit.Celsius;
console.log(unit, Unit.Pascal);
```

Run, the output is `C Pa`.

This output shows an exception to the previous lesson's rule: **an enum is not
erased.** The compiler produces an object that exists at runtime. Unlike a type
annotation, `enum` is not a type, it is a declaration producing both a type and a
value.

A numeric enum makes the produced object do even more:

```javascript
"use strict";
var Status;
(function (Status) {
    Status[Status["Valid"] = 0] = "Valid";
    Status[Status["Suspect"] = 1] = "Suspect";
    Status[Status["Invalid"] = 2] = "Invalid";
})(Status || (Status = {}));
```

This is a two-way mapping giving `1` for `Status.Suspect` and `"Suspect"` for
`Status[1]`. It is a convenience, but it has a cost: every enum adds code to the
bundle output, and cannot be produced correctly when a file is compiled on its own
(without type information). For this reason the compiler has an option that allows
only erasable syntax. With that option on:

```text
status.ts(1,6): error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
```

Which version the option is available from varies by compiler; its behavior is fixed
— it rejects all TypeScript syntax that produces runtime code.

## The Union Alternative

The same constraint can also be expressed in a way that leaves no runtime trace at
all:

```typescript
type Unit = "C" | "Pa" | "%";

const unit: Unit = "C";
console.log(unit);

const invalidUnit: Unit = "F";
```

The diagnostic for the last line:

```text
b4.ts(6,7): error TS2322: Type '"F"' is not assignable to type 'Unit'.
```

The `"C"` here is not a string, it is a **type**: a single-element set containing only
the value `"C"`. Such types are called a **literal type** and will be covered in a
separate lesson. The union of three literal types defines a three-valued set.

Comparison of the two approaches:

| Criterion | `enum` | Union of literal types |
|---|---|---|
| Runtime code | Produces it | Does not produce it |
| Accessing a value | `Unit.Celsius` | `"C"` |
| Matching external data | Requires conversion | Direct |
| Enumerating all values | Via the object | Requires a separate array |

This course will use the union of literal types, since it does not break the type
erasure principle.

## The Measurement Record's First Typed Version

With the tools gathered, the model's first typed version can be written. An object
type lists field names and types inside curly braces:

```typescript
type Measurement = {
  id: string;
  sensor: string;
  value: number;
  unit: "C" | "Pa" | "%";
  time: number;
};

const measurement: Measurement = {
  id: "s-01",
  sensor: "temperature",
  value: 21.4,
  unit: "C",
  time: 1706000000000,
};

console.log(measurement.id, measurement.value, measurement.unit);
console.log(measurement.location);
```

The diagnostic for the last line:

```text
b5.ts(18,25): error TS2339: Property 'location' does not exist on type 'Measurement'.
```

The model now knows three things: which fields exist, each field's type, and the
`unit` field's value set. What it does not know is also visible: the `sensor` field
can hold any string, there is no range for the `value` field, and it is unclear
whether the `time` field is a timestamp or a duration. These gaps will be closed in
later topics.

## Summary

- Primitive types name JavaScript's value model; `number` is a single number type and
  carries the limits of floating-point representation.
- `T[]` and `Array<T>` are the same type; `readonly T[]` is a separate type that does
  not include mutating methods and only holds at compile time.
- A tuple is an array whose element count and each position's type are fixed; element
  names are for documentation.
- An `enum` declaration produces runtime code and is the exception to the type erasure
  principle; a numeric enum also produces a two-way mapping.
- A union of literal types expresses the same constraint without leaving a runtime
  trace.

## Next Step

The measurement record is now typed, but data coming from the outside world has not
entered this type yet. What is the type of the value `JSON.parse` returns, and how is
that value safely converted to the model? The next lesson covers the type system's two
endpoints — `any`, which allows everything, and `unknown`, which allows nothing — and
the `never` type, which has no values at all.
