Introduction
TypeScript is a general-purpose programming language used in a variety of development environments. Over the years since its release, it has gained a lot of support from developers and companies, as evidenced by GitHub ranking it as its fourth most used programming language in 2022. Since then, TypeScript’s prominence has grown.
Furthermore, its popularity is reflected in the abundance of job opportunities, with over 20,000 TypeScript-related job positions advertised on LinkedIn and major industry players, including Trivago and Stripe, choosing to migrate their technology stacks to TypeScript.
In this article, we will explore TypeScript from the ground up to understand its benefits that have led to its increasing use in the developer community.
TypeScript vs. JavaScript
TypeScript is not entirely new. It is a superset of JavaScript that addresses the limitations of JavaScript itself. As a superset, TypeScript extends JavaScript by adding static typing.
This static typing can be declared in two common ways:
1.Type annotations
This involves immediately specifying variables and their types the first time they are declared. Like this:
let name: string = "HygraphWhere name is the name of the variable and string is its type.
2. Type aliases
This method involves defining a type separately using the type keyword and later using it to annotate variables.
type StringType = {
name: string;
};
let name: StringType = { name: "Hygraph" };Now, let's take a look at some of the features that TypeScript offers.
Top Features of TypeScript
Static typing
Static typing refers to when variables are explicitly declared with their data types (such as integer, string, boolean) at compile time and the system performs type checking before executing the program.
While the following behavior is allowed in JavaScript, doing the same in TypeScript will cause an error due to the TypeScript type implementation.
let name: string = "hygraph";
name = 2000Optional typing
While TypeScript allows explicit variable declaration, it also supports writing JavaScript-like code without declaring types. TypeScript infers the type out of the box. For example,
let name = "Hygraph";Above, TypeScript infers that the type name is a string via the variable initialization value, for example, "Hygraph".
TypeScript Generics
By parameterizing types and functions, TypeScript generics create reusable components and functions that can work with different types without compromising type safety:
// Generic function to return the length of an array
function getArrayLength<T>(array: T[]): number {
return array.length;
}
// Using the generic function with different types of arrays
const stringArray: string[] = ["apple", "banana", "orange"];
const numberArray: number[] = [1, 2, 3, 4, 5];
console.log(getArrayLength(stringArray)); // Output: 3
console.log(getArrayLength(numberArray)); // Output: 5Generics are generally useful for defining custom data types that need to work with data types such as trees, graphs, and queues.
Advanced type system
The TypeScript type system goes beyond basic static typing. It also provides features for defining complex types, manipulating types, establishing relationships between variables, enforcing type constraints, and other capabilities that enable error-free application development.
Let's consider some of the most widely used advanced types.
1. Types of Union
Union in TypeScript refers to declaring a variable or function argument that holds multiple data types. This can be useful when a value has different data types at runtime.
Union types are represented by the | symbol, which separates the data types. For example:
let age = number | string;
age = 10;
age = "ten";As seen above, we can specify that the age variable can have two data types. This provides flexibility without compromising type safety.
2. Types of intersections
Crossovers allow the creation of a new type by combining several existing types into a new type. This new type has the properties and functions of the combined types.
Intersections are created using the & symbol between the types to be combined. For example:
interface User {
id: number;
username: string;
email: string;
}
interface Admin {
isAdmin: boolean;
}
// A new type using intersection type
type UserAndAdmin = User & Admin;Here, the User AndAdmin type combines features from both the user interface and administration to produce a type that requires all the features from both.
3. Conditional types
Conditional types in TypeScript create types that depend on a state, which allows for the definition of dynamic types that change based on the type's properties, values, or other context.
Let's consider a basic example:
// A conditional type to determine if a type is an array
type IsArray<T> = T extends any[] ? true : false;
// Test the conditional type with different types
type Result1 = IsArray<number>; // false
type Result2 = IsArray<string[]>; // true
type Result3 = IsArray<boolean | []>; // trueConditional types are defined using the ternary operator (? 🙂) in angle brackets (<>), known as a “generic type parameter” – we’ll talk more about that later in the article. They also use the extends keyword, which checks whether a type meets a certain condition and produces another type based on the result of that condition.
4. Types of Mappings
Mapped types in TypeScript allow you to create new types by converting properties of existing types. They do this by iterating over the properties of a source type and applying a conversion to each property to produce a new type. For example:
// type representing a user
type User = {
id: number;
username: string;
email: string;
};
// Mapped type to make all properties optional
type OptionalUser = { [P in keyof User]?: User[P] };
// New type using the mapped type
const optionalUser: OptionalUser = { id: 1 };
// Property 'email' is now optional
optionalUser.username = "john_doe";
// Property 'id' is still required
// optionalUser.email = "[email protected]"; // Error: Property 'email' is missingMapped types are defined using the syntax { [P in keyof Type]: NewType }, where Type is the source type, P is the property key, and NewType is the converted type. P iterates over the property keys and defines the transformation.
5. Type the nickname
Type aliases allow you to create custom names (aliases) for existing TypeScript types, including primitives, union types, intersection types, and even more complex types like object literals and function types. A type alias is defined using the type keyword followed by the new name, as shown below:
// type alias for a union type
type Result = "success" | "error";
// type alias for an object literal
type Point = { x: number; y: number };
// type alias for a function type
type Greeting = (name: string) => string;
// Using the type aliases
const status: Result = "success";
const origin: Point = { x: 0, y: 0 };
const greet: Greeting = (name) => Hello, ${name}!;The above code shows different uses of type aliases for different types in TypeScript and their use after declaration.
TypeScript in Object-Oriented Programming (OOP)
OOP is a paradigm based on the concept of "objects" that interact to create maintainable and reusable code.
1. TypeScript classes
Classes are patterns or designs for creating objects, that is, they define data (properties) and methods (functions).
Here is an example of how to implement a class in TypeScript:
class Organization {
private name: string;
private yearFounded: number;
constructor(name: string, yearFounded: number) {
this.name = name;
this.yearFounded = yearFounded;
}
public getDetails(): string {
return `${this.name} was founded in ${this.yearFounded}.`;
}
}
let organization = new Organization("Hygraph", 2015);
console.log(organization.name); // Error: Property 'name' is private and only accessible within class 'Organization'.
console.log(organization.getDetails()); // Output: Hygraph was founded in 2015In the code above, we created a class Organisation with private properties named and yearFounded that can only be accessed and modified within the “Organization” class. Notice how the properties and methods are typed.
2. TypeScript interface
Interfaces describe the shape of objects by listing the properties and methods they must have, without providing implementation details:
// Interface representing form data
interface FormData {
firstName: string;
lastName: string;
email: string;
age: number;
}
// Usage
let formData: FormData = {
firstName: "John",
lastName: "Doe",
email: "[email protected]",
age: 30
};In the above example, we defined a FormData interface that represents the structure of the form data.
Next, we created a formData object with properties related to the interface definition.
TypeScript numbers
Enumerate types, represented by enum in TypeScript, are a collection of named constants enclosed in curly braces {}, where each constant has an associated numeric or string value.
A typical TypeScript list collection could look like this:
// enum for days of the week
enum DayOfWeek {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
enum HTTPStatusCodes {
OK = 200,
BadRequest = 400,
NotFound = 404,
}The above code block immediately explains what is happening and demonstrates the semantic benefit of adding meaning to using TypeScript enums.
TypeScript and ES6
TypeScript supports many of the features of ECMAScript 6 (ES6), also known as ECMAScript 2015. Some of these features include:
1) Arrow Functions: Unlike traditional function expressions, arrow functions provide a simple syntax for defining functions. They provide implicit return for single-line expressions and automatic chaining, and can be useful for callback or event handler functions.
// Arrow function
const add = (x, y) => x + y;
// Traditional function expression
const add = function(x, y) {
return x + y;
};2) Template alphabet: This allows you to embed multi-line expressions and strings directly in the code within braces (“). This makes it easy to create dynamic strings, including interpolating variables and expressions.
const name: string = "John";
const greeting: string = `Hello, ${name}!`;There are more ES6 features than this article can cover. For more information, see Ecma International.
Advantages of TypeScript over JavaScript
Since TypeScript was released over a decade ago, there has been ongoing debate about its advantages over JavaScript.
In this section, we will examine some of these benefits.
- Safety Type: TypeScript's static type system applies type annotations to variables, functions, and objects, which helps with early error detection and improves maintainability through interfaces, classes, and modules.
- Better developer experience: Developers can now write code with more confidence, knowing that the compiler will detect potential type mismatches before the code reaches runtime. This can be especially useful when migrating a JavaScript codebase to TypeScript.
When to use TypeScript
From the trade-offs discussed above, you can infer that TypeScript may not be a good fit for all projects. That may be true, but why leave that decision to you? Let us help you make a better decision.
When is it appropriate to use TypeScript?
- Large-scale applications: TypeScript was developed to address the scalability issues of large JavaScript code bases. As the project grows, static typing helps detect errors early, improving code maintainability and collaboration between team members.
- Teamwork: In a team environment with multiple developers contributing to a codebase, TypeScript can facilitate collaboration by enforcing coding conventions and better communication through self-documenting code.
- Long-term projects: Maintenance is the last stage of the software development lifecycle and can last a decade or as short as a year. TypeScript is suitable for long-term projects where code maintainability, scalability, and future-proofing are essential considerations.
When not to use TypeScript
Unfortunately, despite its benefits, TypeScript may not be suitable for all use cases. Such as:
- Simple projects: For small projects with minimal complexity, the benefits of static typing and advanced tooling may not outweigh the configuration and learning curve associated with TypeScript.
- Old codebases: Converting large, existing JavaScript codebases to TypeScript can be daunting. In such cases, the effort required to migrate to TypeScript may not justify the benefits, especially if the project is nearing the end of its life or is undergoing minimal maintenance.









