Basic Types

Reviewed & published by Brayan K

By the end of this lesson you'll be able to label every value in your code with the right type — text, numbers, true/false, lists, and fixed-shape tuples — and you'll know exactly when to reach for any, unknown, or never. This is the foundation that makes every later TypeScript feature click.

Part of the free TypeScript course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

A type is a label on a container. A jar labelled "Sugar" (string) is meant for text; a measuring cup labelled "Cups" (number) is for numbers; a light switch (boolean) is only ever on or off. Plain JavaScript lets you pour anything into any jar and only finds out it was wrong when something breaks at runtime. TypeScript checks the labels before your program runs and stops you pouring salt into the sugar jar. An array is a row of identical jars; a tuple is a labelled tray where slot 1 is always sugar, slot 2 is always flour, in that exact order.

📊 The Core Types at a Glance

TypeHoldsExample
stringTextlet n: string = "Al";
numberWhole or decimallet a: number = 9.99;
booleantrue / falselet ok: boolean = true;
number[]List of one typelet s: number[] = [1, 2];
[string, number]Fixed tuple["Al", 28]
null / undefinedAbsence of a valuelet x: string | null = null;

Note: TypeScript type names are lowercase — use string, not String. The capitalised String is the rarely-needed wrapper object, and using it is a classic beginner slip.

1. Primitive Types

A primitive is a single, simple value. The three you'll use constantly are string (text), number (any number — TypeScript has no separate int/float), and boolean (only true or false). In a .ts file you write the type after a colon: let age: number = 28;. Read this worked example, run it, then you'll write your own.

// A variable's TYPE fixes what kind of value it can hold.
// In TypeScript you can write the type after a colon:  let name: string = "Alice";
// The console.logs below run as plain JavaScript so you can see the values.

// string — text, in "double" or 'single' quotes or `backticks`
let firstName = "Alice";              // type: string
let greeting = `Hello, ${firstName}!`; // backticks let you embed ${...}
console.log(greeting);                // Hello, Alice!

// number — ONE type for integers AND decimals (no separate int/float)
let age = 28;                         // type: number
let price = 9.99;                     // also a number
console.log(age, price);              // 28 9.99

// boolean — only ever true or false (lowercase!)
let isActive = true;                  // type: boolean
console.log("isActive:", isActive);   // isActive: true

// In a .ts file you could write the types explicitly:
//   let firstName: string = "Alice";
//   let age: number = 28;
//   let isActive: boolean = true;

Your turn. The program below is almost complete — fill in the three blanks marked ___ using the hints in the comments, then run it.

// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".

// 1) A string called "city" set to a city name
let city = ___;        // 👉 text in "double quotes"

// 2) A number called "year" set to the current year
let year = ___;        // 👉 a whole number, e.g. 2026

// 3) A boolean called "isOpen" set to true
let isOpen = ___;      // 👉 true or false (no quotes)

// These already work once your variables exist:
console.log(`In ${year}, the shop in ${city} is open: ${isOpen}`);

// ✅ Expected output (example):
//    In 2026, the shop in London is open: true

🔎 Deep Dive: Inference vs Explicit Annotations

You don't always have to write the type. When you give a variable a value, TypeScript infers (figures out) the type for you. Both lines below mean exactly the same thing — the second is just less typing:

let city: string = "London";  // explicit annotation
let city = "London";          // inferred — TypeScript knows it's string

// Either way, this is now an ERROR:
city = 42;  // ❌ Type 'number' is not assignable to type 'string'

When to be explicit: on function parameters and return types, and whenever the value doesn't make the type obvious. When to let it infer: simple let/const with an obvious literal. Idiomatic TypeScript leans on inference — don't clutter let count = 0; with a redundant : number.

🔎 Deep Dive: Literal Types

A type can be narrower than "any string" — it can be one specific value, called a literal type. Combine a few with | ("or") and you get a tidy set of allowed options, like a dropdown menu:

let direction: "up" | "down";  // only these two strings are allowed
direction = "up";              // ✅ fine
direction = "left";            // ❌ '"left"' is not assignable

// This is also why const is "tighter" than let:
const mode = "dark";  // type is the literal "dark", not just string
let theme = "dark";   // type widens to string

Literal types let you describe rules like "status is 'active', 'paused', or 'closed'" and have the compiler reject anything else — far safer than a loose string.

2. Arrays & Tuples

An array holds many values of the same type. You write the type as number[] ("array of number") or, identically, as Array<number> using a generic — pick whichever you find clearer. A tuple is a fixed-length array where each position has its own type, like [string, number, boolean]; it's perfect for a value that always has the same shape, such as a coordinate or a row of data.

// === Arrays — many values of the SAME type ===
// In TypeScript:  let scores: number[] = [95, 87, 92];
// The "number[]" means "an array where every element is a number".
let scores = [95, 87, 92];
console.log("scores:", scores);          // scores: [ 95, 87, 92 ]
console.log("first:", scores[0]);        // first: 95
console.log("how many:", scores.length); // how many: 3

let names = ["Alice", "Bob"];            // string[]
names.push("Charlie");                   // adding another string is fine
console.log("names:", names);            // names: [ 'Alice', 'Bob', 'Charlie' ]

// Array<T> is the SAME thing, written with a generic.
//   let scores: Array<number> = [95, 87, 92];   // identical to number[]

// === Tuples — a FIXED-length array, one type per position ===
// In TypeScript:  let user: [string, number, boolean] = ["Alice", 28, true];
// Position 0 must be string, 1 must be number, 2 must be boolean.
let user = ["Alice", 28, true];
console.log("name:", user[0]);   // name: Alice   (string slot)
console.log("age: ", user[1]);   // age:  28      (number slot)
console.log("active:", user[2]); // active: true  (boolean slot)

// A tuple is great for a value that always has the same shape,
// like an (x, y) coordinate:
let point = [10, 20];            // [number, number]
console.log("x =", point[0], "y =", point[1]); // x = 10 y = 20

Now you try. Build a couple of arrays and a tuple, then run the program to check the output.

// 🎯 YOUR TURN — fill in the blanks marked with ___

// 1) An array of three numbers (the type would be number[])
let temps = ___;          // 👉 e.g. [18, 21, 19]

// 2) Add one more reading to the end of the array
temps.push(___);          // 👉 any number, e.g. 22

// 3) A tuple describing a product: [name (string), price (number)]
let product = ___;        // 👉 e.g. ["Coffee", 2.5]

console.log("readings:", temps);
console.log(`${product[0]} costs £${product[1]}`);

// ✅ Expected output (example):
//    readings: [ 18, 21, 19, 22 ]
//    Coffee costs £2.5

3. any vs unknown vs never

These three special types are about the edges of the type system. any switches checking off — the value can be anything and TypeScript stops protecting you (use it as a last resort). unknown is the safe sibling: it can hold anything too, but TypeScript forces you to narrow (prove) the type before you use it. never is the type with no possible values — the return type of a function that always throws or loops forever.

// === any — turns OFF type checking (use as a last resort) ===
// "any" tells TypeScript "trust me, don't check this".
// It compiles, but you lose every safety net:
let data = 42;
data = "hello";   // with type 'any' this is allowed
data = [1, 2, 3]; // ...and so is this
console.log("data is now:", data); // data is now: [ 1, 2, 3 ]
// The danger: data.foo.bar would COMPILE but 💥 crash at runtime.

// === unknown — the SAFE alternative to any ===
// You CAN store anything in an 'unknown', but you must CHECK its
// type before you use it. TypeScript forces you to be careful.
function describe(value) {
  // "Narrowing": prove what the type is, then use it safely.
  if (typeof value === "string") {
    return "string of length " + value.length;
  } else if (typeof value === "number") {
    return "number doubled = " + value * 2;
  }
  return "some other type";
}
console.log(describe("hi"));   // string of length 2
console.log(describe(21));     // number doubled = 42

// === never — a value that can NEVER happen ===
// A function that always throws (or loops forever) returns 'never'
// because it never produces a normal value.
//   function fail(msg: string): never { throw new Error(msg); }
console.log("'never' = the type with no possible values");

// In a .ts file these annotations would look like:
//   let data: any = 42;
//   let input: unknown = getInput();
//   function fail(msg: string): never { throw new Error(msg); }

🔎 Deep Dive: null and undefined

Both represent "no value", but with different intent. undefined means "this was never set". null means "intentionally empty — there's deliberately nothing here". You allow a value to be missing by adding it to the type with |:

let nickname: string | null = null;   // explicitly "no nickname yet"
nickname = "Ace";                     // later, give it a value

let middleName: string | undefined;   // not set yet -> undefined

// With "strictNullChecks" on (the recommended default), TypeScript
// makes you handle the empty case before using the value:
if (nickname !== null) {
  console.log(nickname.toUpperCase()); // ✅ safe — we checked first
}

This is one of TypeScript's biggest wins: it turns "cannot read property of null" runtime crashes into compile-time errors you fix before shipping.

📝 The Type Syntax (read-only reference)

This is what the same ideas look like with full TypeScript annotations. You can't run it here (it's TS-only syntax), but it's worth reading closely:

// Primitives
let firstName: string = "Alice";
let age: number = 28;
let isActive: boolean = true;

// Arrays — two identical ways to write the type
let scores: number[] = [95, 87, 92];
let scores2: Array<number> = [95, 87, 92];

// Tuple — one type per position, fixed length
let user: [string, number, boolean] = ["Alice", 28, true];

// Absence of a value
let nickname: string | null = null;
let middleName: string | undefined;

// Literal types — a fixed set of allowed values
let status: "active" | "paused" | "closed" = "active";

// The special types
let anything: any = 42;          // checking OFF (avoid)
let safe: unknown = getInput();  // must narrow before use
function fail(msg: string): never { throw new Error(msg); }

Pro Tips

Common Errors (and the fix)

📋 Quick Reference — Types

TypeMeansExample
stringTextlet s: string = "hi"
numberAny numberlet n: number = 42
booleantrue / falselet b: boolean = true
T[] / Array<T>List of one typelet a: number[] = [1, 2]
[A, B]Fixed tuple["Al", 28]
"a" | "b"Literal unionlet m: "on" | "off"
null / undefinedNo valuestring | null
anyChecking off (avoid)let x: any = 1
unknownSafe "anything"let x: unknown = f()
neverNo possible valuefunction fail(): never

Frequently Asked Questions

Q: Is there really no separate type for integers and decimals?

Correct — like JavaScript, TypeScript has one number type for both 28 and 9.99. (There's also bigint for very large whole numbers, but you'll rarely need it.)

Q: What's the real difference between any and unknown?

Both can hold any value. With any you can then do anything with it and TypeScript won't complain (unsafe). With unknown you must first check the type (typeof, Array.isArray, etc.) before using it, so mistakes are caught at compile time.

Q: When should I write the type and when should I let TypeScript guess?

Let it infer simple initialised variables (let name = "Al"). Be explicit on function parameters, function return types, and any case where the value alone doesn't make the intended type obvious.

Q: Array or tuple — how do I choose?

Use an array when you have any number of items of the same type (number[]). Use a tuple when the length is fixed and each slot means something specific ([name, age]).

Mini-Challenge: Ticket Summary

No blanks this time — just a brief and a starting outline. Build it, run it, and check your output against the example in the comments. This is the kind of small, typed program real apps are made of.

// 🎯 MINI-CHALLENGE: Movie ticket summary
// In a real .ts file you'd annotate the types; here just build the values.
//
// 1. A string "title" (the film name) and a number "tickets".
// 2. A number "pricePerTicket" (e.g. 12.5).
// 3. A boolean "isMember" (members get 10% off — your choice true/false).
// 4. Work out total = tickets * pricePerTicket, then apply the
//    member discount if isMember is true.
// 5. Print:  "<tickets> tickets for <title>: £<total>"
//
// ✅ Example (3 tickets, £12.5 each, member=true):
//    3 tickets for Dune: £33.75

// your code here

🎉 Lesson Complete

Practice quiz

Which type does TypeScript use for both 28 and 9.99?

  • int
  • float
  • number
  • decimal

Answer: number. TypeScript (like JavaScript) has one number type for integers and decimals alike; there is no separate int or float.

How do you write the type for an array where every element is a number?

  • number[]
  • [number]
  • Array(number)
  • numbers

Answer: number[]. number[] (equivalently Array<number>) means an array whose every element is a number. [number] would be a one-element tuple.

What does the tuple type [string, number, boolean] describe?

  • An array of any of those three types in any order
  • Exactly three elements: a string, then a number, then a boolean
  • An object with three keys
  • A union of string, number, and boolean

Answer: Exactly three elements: a string, then a number, then a boolean. A tuple is a fixed-length array with a type per position: index 0 must be a string, 1 a number, 2 a boolean.

What does typing a value as any do?

  • Restricts it to primitive values
  • Turns OFF type checking for that value
  • Forces you to narrow before using it
  • Makes it read-only

Answer: Turns OFF type checking for that value. any switches off type checking entirely, so even nonsense like data.foo.bar compiles and may crash at runtime.

What is the safe alternative to any that forces you to check the type before use?

  • never
  • void
  • unknown
  • object

Answer: unknown. unknown accepts any value but won't let you use it until you narrow it with a check like typeof, keeping the safety net.

What is the never type?

  • A value that is always null
  • The type with no possible values
  • Another name for any
  • A type for empty strings

Answer: The type with no possible values. never is the type with no possible values - for example the return type of a function that always throws or loops forever.

Which is the correct, idiomatic primitive type name in TypeScript?

  • String
  • Str
  • string
  • Text

Answer: string. Type names are lowercase: string, number, boolean. Capitalised String is the wrapper object and is a classic beginner slip.

Given let mode = "dark" vs const mode = "dark", how do their inferred types differ?

  • Both are the literal type "dark"
  • let is the literal "dark"; const widens to string
  • let widens to string; const is the literal "dark"
  • Both widen to string

Answer: Both widen to string. A const holds the narrow literal type "dark", while a let widens to the broader type string because it can be reassigned.

How do you allow a variable to be a string OR explicitly null?

  • string null
  • string | null
  • string & null
  • string?

Answer: string | null. A union with | lets a value be one of several types: string | null permits a string or the value null.

When is it most idiomatic to write an explicit type annotation?

  • On every let and const, always
  • Never - always rely on inference
  • On function parameters and return types
  • Only on boolean variables

Answer: On function parameters and return types. Let inference handle obvious initialised variables; be explicit on function parameters and return types where intent isn't obvious.

Continue this course