Free Handbook · Runs in your browser

Basic Types

Primitives, arrays, tuples, the any/unknown/never triad, literal types, and enums — the vocabulary every other TypeScript concept builds on.

0 / 105 lessons🔥 0 day streak
ShareXLinkedIn

Module 01 · what you'll be able to do

  • Choose between string, number and boolean annotations and read them back from a variable
  • Tell a fixed-shape tuple apart from an open-ended array and know when each is idiomatic
  • Explain why any opts out of checking, unknown forces a check first, and never marks unreachable code
  • Narrow a value to an exact set of literals with a union type instead of reaching for an enum
  • Pick a numeric enum, a string enum, or a literal union for a fixed set of options
01

Primitives: string, number, boolean

TypeScript adds a type-checking layer on top of JavaScript, but the values at runtime are exactly the same values JavaScript has always had. The three you reach for constantly are string, number and boolean — lower-case, because TypeScript reserves the capitalised String, Number and Boolean for the rarely-used wrapper object types. An annotation is the : type after a name; it tells the compiler what is allowed, and disappears completely once the code is compiled to JavaScript.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
Ada 36 true
Your turn
Add a fourth const with a string annotation and print it alongside the others.
One number type, not several
Unlike Java or C#, TypeScript has a single number type for every numeric value — integers and floats both. There is no separate int or float. Arbitrarily large whole numbers use bigint instead, written with an n suffix like 10n.
Error you will hit

TS2322: assigning the wrong type

typescript
let age: number
age = "thirty"
app.ts:2:1 - error TS2322: Type 'string' is not assignable to type 'number'.

2 age = "thirty"
  ~~~
Why the compiler said that

The declaration let age: number commits age to holding only numbers for its entire lifetime. Assigning a string violates that contract, and the compiler catches it before the code ever runs — a whole class of bug JavaScript alone cannot see.

The fix

Either assign a real number, or, if the annotation was wrong, change it to the type you actually meant.

typescript
let age: number
age = 30
Primitive
An immutable value that is not an object: string, number, boolean, null, undefined, bigint, symbol.
Type annotation
The : Type written after a name to declare what values it may hold. Erased entirely at compile time — it produces no JavaScript.
Static typing
Checking types before the program runs (at compile time) rather than while it runs. TypeScript's whole job.
02

Arrays: number[] and Array<number>

An array type says two things: "this is a list" and "every element has this type". You can write it two ways — number[] or Array<number> — and they mean exactly the same thing. The bracket form is far more common in everyday code; the generic form shows up more once you are comfortable with generics in general, or when the element type itself is already a union and brackets would be ambiguous.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
1, 2, 3
Ada, Grace
3 2
Your turn
Try pushing a string onto nums and read the error TypeScript would give (the sandbox itself will not stop you, but a real compiler would).
FormExampleWhen to reach for it
T[]number[], string[]The default. Shorter, reads naturally for a simple element type.
Array<T>Array<number>When T is itself a union, e.g. Array<string | number>, so brackets do not have to fight for attention.
readonly T[]readonly number[]An array whose elements cannot be reassigned by index and that has no push/pop — covered with as const in Module 02.
An array type is not a tuple
number[] says nothing about length — it could have zero elements or a thousand. If you need a fixed number of slots with a specific type in each one, that is a tuple, covered next.
03

Tuples: fixed-length, fixed-type arrays

A tuple is written like an array literal of types — [string, number] — and it fixes both the length and the type at each position. Position 0 must be a string, position 1 must be a number, and (by default) that is the whole array. Tuples are how you type things like coordinate pairs, [key, value] entries, or a function that returns two values at once.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
10 20
age: 36
Your turn
Destructure point into const [x, y] = point and print x and y instead of indexing.
Error you will hit

TS2322: wrong type at a tuple position

typescript
const point: [number, number] = [10, "20"]
app.ts:1:39 - error TS2322: Type 'string' is not assignable to type 'number'.

1 const point: [number, number] = [10, "20"]
                                        ~~~~
Why the compiler said that

Each slot in a tuple type is checked individually against the literal at that position. Position 1 is declared number, and "20" is a string, so it fails even though the tuple as a whole is still length 2.

The fix

Drop the quotes so the second element is a real number literal.

typescript
const point: [number, number] = [10, 20]
Tuple
An array type with a fixed length and a specific type per position, e.g. [string, number].
Tuple destructuring
Unpacking a tuple by position into named variables: const [key, value] = entry.
JuniorWhat is a tuple in TypeScript, and how is it different from an array type like number[]?

A tuple, written [string, number], fixes both the length and the type of each position. number[] only fixes that every element is a number — the length is open-ended and every position has the same type. Tuples are used for structured, fixed-shape data like coordinate pairs; arrays are used for lists of unknown length.

What they are really testing: Whether the candidate understands tuples are a positional contract, not just "a short array".

04

any vs unknown vs never

any, unknown and never sit at the edges of the type system and are easy to mix up. any tells the compiler to stop checking — you can call anything on it, assign it anywhere, and get no warnings, which makes it a trapdoor out of type safety rather than a type. unknown is the type-safe version: it can hold anything, but the compiler will not let you do anything with it until you narrow it — with typeof, instanceof, or a custom check — to something more specific. never is the type of a value that can never exist: a function that always throws, or one with an infinite loop, returns never.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
NOW A STRING
HELLO
Your turn
Remove the typeof u === "string" check and call u.toUpperCase() directly — a real compiler refuses it; the next block shows exactly why.
Error you will hit

TS2571: using unknown before narrowing it

typescript
let u: unknown = "hello"
console.log(u.toUpperCase())
app.ts:2:14 - error TS2571: Object is of type 'unknown'.

2 console.log(u.toUpperCase())
               ~
Why the compiler said that

unknown is deliberately opaque. The compiler has no idea what is inside it, so it refuses every property access and method call until you prove — with a type guard like typeof — what the value actually is.

The fix

Guard the value first, the same way the run block above does, so the compiler can narrow u to string inside the branch.

typescript
let u: unknown = "hello"
if (typeof u === "string") {
  console.log(u.toUpperCase())
}
typescriptnever.ts
function fail(message: string): never {
  throw new Error(message)
}

function loopForever(): never {
  while (true) {
    // never returns
  }
}

A function annotated never is a promise to the caller: this call will not complete normally — it always throws or never returns. The compiler also infers never on its own for the unreachable branch of an exhaustive switch.

TypeCan holdCan you use it directly?Typical use
anyAnythingYes — no checking at allEscape hatch; avoid in new code
unknownAnythingNo — must narrow firstData from outside the program: JSON, API responses, user input
neverNothingN/A — the value never existsFunctions that always throw; exhaustiveness checks
Quick check

Which type would you give a variable that could hold anything, while still forcing every caller to check what it actually is before using it?

05

Literal types: "left" | "right"

A literal type narrows a general type down to one exact value — not "a string", but the string "left" specifically. On their own they are not very useful, but combined with the union operator | they describe a closed, exact set of allowed values, which is one of TypeScript's most practical patterns for options, modes and states.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
Moving left
Moving right
Your turn
Add "up" to the Direction union, then call move("up").
Error you will hit

TS2345: a value outside the literal union

typescript
type Direction = "left" | "right"

function move(direction: Direction) {
  console.log("Moving", direction)
}

move("up")
app.ts:6:6 - error TS2345: Argument of type '"up"' is not assignable to parameter of type '"left" | "right"'.

6 move("up")
       ~~~~
Why the compiler said that

The union "left" | "right" is a closed list — nothing else is a valid Direction, no matter how reasonable it looks. This is exactly the point: it turns a typo or an unsupported option into a compile error instead of a bug that only shows up at runtime.

The fix

Either pass one of the allowed literals, or add "up" to the type if it should genuinely be supported.

typescript
type Direction = "left" | "right" | "up"

function move(direction: Direction) {
  console.log("Moving", direction)
}

move("up")
  • Function options: type Align = "start" | "center" | "end"
  • State machines: type Status = "idle" | "loading" | "success" | "error"
  • HTTP-adjacent code: type Method = "GET" | "POST" | "PUT" | "DELETE"
  • Anywhere you would reach for an enum in another language — see the enums lesson for the tradeoff
06

Type aliases: type Direction = ...

The type keyword gives a name to any type — a primitive, a union, an object shape, a function signature, anything the type system can express. It does not create a new type, just a shorter name for an existing one, which is why two variables with the aliased type and the type it points to are completely interchangeable.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
ID: 101
ID: abc123
Your turn
Add a third member to the ID union — say boolean — and pass a boolean to printId.
A name for readability, not a runtime thing
Like every type in TypeScript, type ID = string | number is erased completely when the code compiles — it exists purely to make the rest of the file more readable and to give you one place to change the definition. Module 04 covers the fuller comparison between type and interface.
Type alias
A name, declared with type, that stands in for another type. Purely a compile-time convenience.
Union type
A type built with | meaning "one of these": string | number is a string or a number, never both at once.
Mid-levelWhat does a type alias actually do at runtime?

Nothing — type aliases, like all TypeScript types, are erased entirely during compilation. type ID = string | number produces zero JavaScript output. It only exists to give the type checker (and the reader) a name for a type, so it is purely a compile-time convenience, not a runtime construct like a class.

What they are really testing: Whether the candidate has internalised that TypeScript types are compile-time only, a distinction that trips people up when they expect a type alias to behave like a class or a runtime check.

07

Enums: numeric, string, and when to use a union instead

An enum gives a name to a fixed set of related constants. Unlike an interface or a type alias, an enum is not erased — it compiles down to a real JavaScript object that exists at runtime. There are two flavours: a numeric enum, where members default to 0, 1, 2, …, and a string enum, where every member must have its own explicit string value.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
1
Green
Your turn
Add a fourth member, Purple, after Blue, and print Color.Purple — predict the number before you check.
typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
ACTIVE
INACTIVE
VisualizeWhat a numeric enum actually compiles toStep 1 / 5
enum Color { Red, Green, Blue }
console.log(Color.Green)
console.log(Color[1])
Line 1

A numeric enum is not erased like a type alias — it compiles to a real object built at runtime. Red gets 0, and because this is a numeric enum, a reverse mapping is also written: Color[0] = "Red".

Variables now
Color.Red0
Color[0]'Red'
All 5 steps as a table
StepLineWhat happenedVariables now
11A numeric enum is not erased like a type alias — it compiles to a real object built at runtime. Red gets 0, and because this is a numeric enum, a reverse mapping is also written: Color[0] = "Red".Color.Red = 0 Color[0] = 'Red'
21Green gets 1, with its own reverse mapping Color[1] = "Green".Color.Green = 1 Color[1] = 'Green'
31Blue gets 2, with reverse mapping Color[2] = "Blue".Color.Blue = 2 Color[2] = 'Blue'
42Look up the Green property on the compiled Color object — it holds 1.
53Index the same object with the number 1 — the reverse mapping returns the string "Green".
String enums have no reverse mapping
The Color[1] trick above only works for numeric enums. A string enum like Status only maps forward — Status.Active works, but there is no Status["ACTIVE"] reverse lookup, because the compiler cannot invert a mapping where the values are not guaranteed unique the same way.

Numeric / string enum

  • Produces a real object at runtime — costs bytes in the compiled output
  • Members are accessed as Color.Green, comparisons use ===
  • Numeric enums accept any number where the enum type is expected — weaker checking than you'd expect
  • Good fit when you need the runtime object: iterating members, reverse lookup

Union of string literals

  • Erased completely at compile time — zero runtime cost
  • Values ARE plain strings, so they serialise to JSON and log readably with no extra step
  • Fully closed — only the exact literals in the union are ever valid, nothing accepted by accident
  • The more common modern choice for simple fixed-option types
Error you will hit

TS2339: referencing a member that does not exist

typescript
enum Color { Red, Green, Blue }
console.log(Color.Purple)
app.ts:2:19 - error TS2339: Property 'Purple' does not exist on type 'typeof Color'.

2 console.log(Color.Purple)
                    ~~~~~~
Why the compiler said that

Enums are closed sets, same as a literal union — the compiler knows every member of Color at compile time and flags any name that is not one of them, the same way it would flag a typo on any other object.

The fix

Add the member to the enum declaration, or fix the typo if one was intended.

typescript
enum Color { Red, Green, Blue, Purple }
console.log(Color.Purple)
Mid-levelWhen would you prefer a union of string literals over an enum?

For most simple fixed-option types — a status, a direction, an alignment — a literal union like type Status = "active" | "inactive" is preferred: it is erased entirely (no runtime cost or bundle size), the values are plain strings that serialise and log without translation, and it is just as closed a set as an enum. Reach for an enum only when you actually need the runtime object — iterating its members, or a genuine reverse lookup from value to name.

What they are really testing: Whether the candidate has an opinion beyond "enums are for fixed sets" and understands the erasure tradeoff.

SeniorWhy is never useful as a return type, and where does TypeScript infer it for you without being asked?

It documents and enforces that a function never returns control to its caller — always throwing or looping forever — which lets the compiler treat code after the call as unreachable. TypeScript also infers never on its own at the default branch of an exhaustive switch over a union: if every case is handled, the fallback variable has type never, and assigning it to a never-typed variable is a compile-time trip-wire that fires the moment someone adds a new union member without updating the switch.

What they are really testing: Whether the candidate knows the exhaustiveness-checking pattern, one of the most practical uses of never in real codebases.

Finish the TypeScript handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.