Understanding TypeScript Types: Primitives, Objects, and Type Manipulations

TypeScript Types

TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. TypeScript types are categorized into primitive types and object types. The basic types in TypeScript include string, boolean, number, array, tuple, and enum. TypeScript’s type system allows you to build new types out of existing ones using a large variety of operators.

There are several types that you can use to define variables. Here are some commonly used types:

Primitives Types:

  • number: Represents numeric values like integers, floats, etc.
  • string: Represents textual data enclosed in single quotes (”) or double quotes (“”).
  • boolean: Represents a logical value, either true or false.
  • null: Represents the absence of any object value.
  • undefined: Represents an uninitialized variable or an object that lacks a value.
  • symbol: Represents unique and immutable values that can be used as identifiers.

Object Types:

  • object: Represents a non-primitive type, i.e., anything that is not a number, string, boolean, null, or undefined.
  • array: Represents an ordered list of values of a specific type. You can define an array using square brackets ([]).
  • tuple: Represents an array with a fixed number of elements, where each element can have a different type. Tuples allow you to specify the type of each element in a specific order.
  • enum: Represents a set of named constants. Enums allow you to define a collection of related values that can be assigned to a variable.
  • function: Represents a function type. You can define the parameter types and return type of a function using function types.

Other Types:

  • any: Represents a dynamic type that can hold any value. When a variable is of type any, TypeScript doesn’t perform type checking for that variable.
  • unknown: Represents a type-safe counterpart of any. Variables of type unknown can hold any value, but TypeScript enforces type checking before performing any operations on them.
  • void: Represents the absence of any type. Typically used as the return type of functions that don’t return a value.
  • never: Represents a type for values that never occur. It is used for functions that don’t have a reachable endpoint or for variables that cannot have a value.
  • literal types: Represents a specific value that a variable can hold. For example, you can define a variable of type 'success', which can only have the value 'success'.

These are some of the commonly used types in TypeScript. Understanding and utilizing these types can help you write more robust and type-safe code.

Type Assertion

Type Assertion is used in TypeScript to override the type inferred by the compiler. It is a mechanism that tells the compiler about the type of a variable. In TypeScript, type assertions can be performed using the “angle bracket” syntax or the “as” keyword.

Here’s an example of Type Assertion in TypeScript:

let code: any = 123;
let employeeCode = <number> code;
console.log(typeof(employeeCode)); // Output: number

In this example, we have a variable code of type any. We assign the value of this variable to another variable called employeeCode using type assertion. The compiler is informed that code is of type number. The typeof operator confirms that employeeCode is indeed of type number.

Non-null Assertion

The non-null assertion operator (!) tells the TypeScript compiler that a value typed as optional cannot be null or undefined. It is placed after the variable name and is used to assert that the variable is not null or undefined.

Here’s an example of how to use the non-null assertion operator in TypeScript:

let word: string | null = null;
const num = 1;
if (num) {
  word = "Hello World!";

In this example, the variable word is of type string or null. The non-null assertion operator (!) is used to assert that word is not null. Therefore, we can safely call the toLowerCase method on word.

Type Inference

Type inference is used in TypeScript to provide type information when there is no explicit type annotation. It occurs when initializing variables, setting parameter default values, and determining function return types. TypeScript uses the best common type algorithm to select the best candidate types that are compatible with all variables. TypeScript also uses contextual typing to infer types of variables based on their usage in the code.

Here’s an example of how type inference works in TypeScript:

let x = 3;

In this example, the type of the variable x is inferred to be number because it is initialized with a numeric value.

Type Compatibility

TypeScript uses a structural type system, where a type is compatible with another type if it has at least the same members. TypeScript’s type compatibility is based on the structure and shape of types rather than their explicit declarations.

Here’s an example of type compatibility in TypeScript:

interface Pet {
  name: string;

let pet: Pet;
let dog = { name: "Lassie", owner: "Rudd Weatherwax" };
pet = dog;

In this example, the Pet interface defines a property name. We declare a variable pet of type Pet and assign it an object dog with a name property. TypeScript considers the object dog to be compatible with the Pet interface because it has at least the same members.

Combining Types

In TypeScript, combining types refers to the ability to create new types by combining existing types in various ways. These combinations can be done using union types, intersection types, and conditional types. Let’s explore each of these combining types in TypeScript:

Union Types

Union types allow you to combine multiple types using the | (pipe) symbol.

A value of a union type can be of any of the constituent types. For example:

   type MyType = string | number;
   let x: MyType;
   x = "hello"; // valid
   x = 10; // valid
   x = true; // invalid, as boolean is not in the union

Union types are useful when a value can be one of multiple types.

Intersection Types:

Intersection types allow you to combine multiple types into a single type that has all the properties and methods of each constituent type. It is denoted by the & (ampersand) symbol. For example:

   type FirstType = { name: string };
   type SecondType = { age: number };

   type CombinedType = FirstType & SecondType;

   let obj: CombinedType = { name: "John", age: 25 };
   console.log(obj.name); // "John"
   console.log(obj.age); // 25

Intersection types are useful when you want to create a type that has the combined features of multiple types.

Conditional Types:

Conditional types introduce type transformations based on a condition. They are defined using the extends keyword and are denoted by the ? (question mark) symbol. Conditional types are evaluated dynamically based on the given type parameter. For example:

   type NonNullable<T> = T extends null | undefined ? never : T;

   let value: NonNullable<string | null>;
   value = "hello"; // valid
   value = null; // invalid

   let numberValue: NonNullable<number | undefined>;
   numberValue = 10; // valid
   numberValue = undefined; // invalid

In this example, the NonNullable conditional type transforms the given type parameter by excluding null and undefined types.

These combining types provide powerful ways to create complex types in TypeScript, allowing you to express a wide range of scenarios and enforce type safety in your code.

Type Guards and Narrowing in TypeScript

Type guards in TypeScript allow you to narrow down the type of a value within a conditional block based on a specific condition. Type narrowing is the process of reducing the possible types of a value within a certain scope. This helps you write more precise and type-safe code. Here’s an example that demonstrates type guards and narrowing:

interface Circle {
  kind: "circle";
  radius: number;

interface Square {
  kind: "square";
  sideLength: number;

type Shape = Circle | Square;

function getArea(shape: Shape): number {
  if (shape.kind === "circle") {
    // Type guard narrows down the type to Circle
    return Math.PI * shape.radius * shape.radius;
  } else if (shape.kind === "square") {
    // Type guard narrows down the type to Square
    return shape.sideLength * shape.sideLength;
  } else {
    // This code is unreachable because Shape only allows Circle or Square
    throw new Error("Invalid shape");

const circle: Circle = { kind: "circle", radius: 5 };
console.log(getArea(circle)); // Output: 78.53981633974483

const square: Square = { kind: "square", sideLength: 4 };
console.log(getArea(square)); // Output: 16

In this example, we have defined two interfaces, Circle and Square, both of which have a kind property indicating the type of the shape. The Shape type is a union of Circle and Square. The getArea function takes a Shape parameter and uses type guards to narrow down the type within the conditional blocks.

When we call getArea with a Circle object, the type guard shape.kind === "circle" allows TypeScript to infer that the shape parameter is of type Circle within that block. Similarly, when shape.kind === "square", TypeScript knows that the shape parameter is of type Square. This narrowing enables us to access the specific properties of each shape without type errors.

Type guards are a powerful feature in TypeScript, allowing you to write more reliable and type-safe code by narrowing down the possible types of values based on conditions.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>