beginner Step 2 of 18

Basic Types

TypeScript Programming

Basic Types in TypeScript

TypeScript provides a rich set of basic types that map to JavaScript's runtime types while adding compile-time safety. Understanding these foundational types is essential because every variable, parameter, and return value in a well-typed TypeScript program carries a type annotation. The compiler uses these annotations to verify that values are used consistently throughout your code, eliminating entire categories of bugs before they reach users.

Primitive Types

TypeScript's primitive types mirror JavaScript's: string, number, and boolean. Unlike some languages that distinguish between integers and floating-point numbers, TypeScript (like JavaScript) has a single number type that covers both.

// String type
let firstName: string = "Alice";
let greeting: string = `Hello, ${firstName}`;

// Number type — integers and floats alike
let age: number = 30;
let price: number = 19.99;
let hex: number = 0xff;
let binary: number = 0b1010;

// Boolean type
let isActive: boolean = true;
let hasPermission: boolean = false;

The any Type

The any type opts out of type checking entirely. A variable of type any can hold any value and can be assigned to any other variable without compiler complaints. While useful during migration from JavaScript, overusing any defeats the purpose of TypeScript. Prefer unknown when you genuinely do not know the type — it is safer because it forces you to narrow the type before using the value.

let flexible: any = "hello";
flexible = 42;       // no error
flexible = true;     // no error
flexible.foo.bar;    // no error (but will crash at runtime!)

// unknown is safer — must narrow before use
let safe: unknown = getData();
if (typeof safe === "string") {
  console.log(safe.toUpperCase()); // OK after narrowing
}

void, null, and undefined

The void type represents the absence of a return value and is most commonly used as the return type of functions that do not return anything. The types null and undefined each have their own type with the same name. When strictNullChecks is enabled (recommended), null and undefined are not assignable to other types unless explicitly included in a union.

// void — function returns nothing
function logMessage(msg: string): void {
  console.log(msg);
  // no return statement
}

// null and undefined as types
let nothing: null = null;
let notAssigned: undefined = undefined;

// With strictNullChecks, this is an error:
// let name: string = null; // Error!

// Use a union to allow null
let name: string | null = null;
name = "Alice"; // OK

never and bigint

The never type represents values that never occur — for example, a function that always throws an error or has an infinite loop. The bigint type handles arbitrarily large integers when number is not sufficient.

// never — function that never returns
function throwError(message: string): never {
  throw new Error(message);
}

// bigint — for very large integers
let huge: bigint = 9007199254740991n;
let alsoHuge: bigint = BigInt("9007199254740991");

Type Inference

TypeScript can infer types from assigned values, so you do not always need explicit annotations. However, explicit annotations improve readability and catch mistakes at the point of declaration rather than at the point of use.

// Inferred as string — no annotation needed
let city = "Paris";

// Inferred as number
let count = 0;

// Explicit is clearer for function signatures
function add(a: number, b: number): number {
  return a + b;
}
Tip: Let TypeScript infer local variable types when the value makes the type obvious. Always annotate function parameters and return types explicitly for clarity and safety.

Key Takeaways

  • string, number, and boolean are the core primitive types in TypeScript.
  • Avoid any whenever possible; prefer unknown for truly unknown types.
  • void is used for functions that return nothing; never is for functions that never return.
  • Enable strictNullChecks and use union types like string | null to handle nullable values safely.
  • TypeScript infers types when possible, but explicit annotations on function signatures are best practice.