You are on page 1of 20

24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT

Open in app Sign up Sign In

Published in ITNEXT

This is your last free member-only story this month.


Sign up for Medium and get an extra one

Vitalii Shevchuk Follow

Jan 20 · 14 min read · · Listen

Save

🔥 Mastering TypeScript: 20 Best Practices for


Improved Code Quality
Achieve Typescript mastery with a 20-steps guide, that takes you from Padawan to
Obi-Wan.

Content
Intro 830 20

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 1/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
Best Practice 1: Strict Type Checking

Best Practice 2: Type Inference

Best Practice 3: Linters

Best Practice 4: Interfaces

Best Practice 5: Type Aliases

Best Practice 6: Using Tuples

Best Practice 7: Using any Type

Best Practice 8: Using the unknown Type

Best Practice 9: “never”

Best Practice 10: Using the keyof operator

Best Practice 11: Using Enums

Best Practice 12: Using Namespaces

Best Practice 13: Using Utility Types

Best Practice 14: “Readonly” and “ReadonlyArray”

Best Practice 15: Type Guards

Best Practice 16: Using Generics

Best Practice 17: Using the infer keyword

Best Practice 18: Using Conditional Types

Best Practice 19: Using Mapped Types

Best Practice 20: Using Decorators

Conclusion

Learn More

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 2/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT

Intro
TypeScript is a widely used, open-source programming language that is perfect for
modern development. With its advanced type system, TypeScript allows developers
to write code that is more robust, maintainable, and scalable. But, to truly harness
the power of TypeScript and build high-quality projects, it’s essential to understand
and follow best practices. In this article, we’ll dive deep into the world of TypeScript
and explore 20 best practices for mastering the language. These best practices cover
a wide range of topics and provide concrete examples of how to apply them in real-
world projects. Whether you’re just starting out or you’re an experienced TypeScript
developer, this article will provide valuable insights and tips to help you write clean,
efficient code.

So, grab a cup of coffee ☕️, and let’s get started on our journey to mastering
TypeScript!

Best Practice 1: Strict Type Checking


We will start with the most basic ones. Imagine being able to catch potential errors
before they even happen, sounds too good to be true? Well, that’s exactly what strict
type checking in TypeScript can do for you. This best practice is all about catching
those sneaky bugs that can slip into your code and cause headaches down the line.

Strict type checking is all about making sure that the types of your variables match
the types you expect them to be. This means that if you declare a variable to be of
type string , TypeScript will make sure that the value assigned to that variable is
indeed a string and not a number, for example. This helps you to catch errors early
on and make sure your code is working as intended.

Enabling strict type checking is as simple as adding “strict”: true to your


tsconfig.json file (has to be true by default). By doing this, TypeScript will enable a
set of checks that will catch certain errors that would otherwise go unnoticed.

Here’s an example of how strict type checking can save you from a common
mistake:

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 3/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT

let userName: string = "John";


userName = 123; // TypeScript will raise an error because "123" is not a string

By following this best practice, you will be able to catch errors early on and make
sure that your code is working as intended, saving you time and frustration in the
long run.

Best Practice 2: Type Inference


TypeScript is all about being explicit with your types, but that doesn’t mean you
have to spell everything out.

Type inference is the ability of the TypeScript compiler to automatically determine


the type of a variable based on the value that is assigned to it. This means that you
don’t have to explicitly specify the type of a variable every time you declare it.
Instead, the compiler will look at the value and infer the type for you.

For example, in the following code snippet, TypeScript will automatically infer that
the type of name is a string :

let name = "John";

Type inference is especially useful when you are working with complex types or
when you are initializing a variable with a value that is returned from a function.

But remember, type inference is not a magic wand, sometimes it’s better to be
explicit with types, especially when working with complex types or when you want
to make sure that a specific type is used.

Best Practice 3: Linters


https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 4/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
Linters are tools that can help you to write better code by enforcing a set of rules
and guidelines. They can help you to catch potential errors and improve the overall
quality of your code.

There are several linters available for TypeScript, such as TSLint and ESLint, that
can help you to enforce a consistent code style and catch potential errors. These
linters can be configured to check for things like missing semicolons, unused
variables, and other common issues.

Best Practice 4: Interfaces


When it comes to writing clean and maintainable code, interfaces are your best
friend. They are like a blueprint for your objects, outlining the structure and
properties of the data you will be working with.

An interface in TypeScript defines a contract for the shape of an object. It specifies


the properties and methods that an object of that type should have, and it can be
used as a type for a variable. This means that when you assign an object to a
variable with an interface type, TypeScript will check that the object has all the
properties and methods specified in the interface.

Here’s an example of how to define and use an interface in TypeScript:

interface User {
name: string;
age: number;
}
let user: User = {name: "John", age: 25};

Interfaces also make it easier to refactor your code, by ensuring that all the places
where a certain type is used are updated at once.

Best Practice 5: Type Aliases

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 5/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
TypeScript allows you to create custom types using a feature called type aliases. The
main difference between features type alias and interface is that type alias

creates a new name for the type, whereas interface creates a new name for the
shape of the object.

For example, you can use a type alias to create a custom type for a point in a two-
dimensional space:

type Point = { x: number, y: number };


let point: Point = { x: 0, y: 0 };

Type aliases can also be used to create complex types, such as a union type or an
intersection type.

type User = { name: string, age: number };


type Admin = { name: string, age: number, privileges: string[] };
type SuperUser = User & Admin;

Best Practice 6: Using Tuples


Tuples are a way to represent a fixed-size array of elements with different types.
They allow you to express a collection of values with a specific order and types.

For example, you can use a tuple to represent a point in a 2D space:

let point: [number, number] = [1, 2];

You can also use a tuple to represent a collection of multiple types:

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 6/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT

let user: [string, number, boolean] = ["Bob", 25, true];

One of the main advantages of using tuples is that they provide a way to express a
specific type relationship between the elements in the collection.

In addition, you can use destructuring assignment to extract the elements of a tuple
and assign them to variables:

let point: [number, number] = [1, 2];


let [x, y] = point;
console.log(x, y);

Best Practice 7: Using any Type


Sometimes, we may not have all the information about a variable’s type, but still
need to use it in our code. In such cases, we can utilize the any type. But, like any
powerful tool, the use of any should be used with caution and purpose.

One best practice when using any is to limit its usage to specific cases where the
type is truly unknown, such as when working with third-party libraries or
dynamically generated data. Additionally, it’s a good idea to add type assertions or
type guards to ensure the variable is being used correctly. And when possible, try to
narrow down the type of the variable as much as you can.

For example:

function logData(data: any) {


console.log(data);
}

const user = { name: "John", age: 30 };


const numbers = [1, 2, 3];

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 7/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
logData(user); // { name: "John", age: 30 }
logData(numbers); // [1, 2, 3]

Another best practice is to avoid using any in function return types and function
arguments, as it can weaken the type safety of your code. Instead, you can use a
type that is more specific or use a type that is more general like unknown or object

that still provide some level of type safety.

Best Practice 8: Using the unknown Type


The unknown type is a powerful and restrictive type that was introduced in
TypeScript 3.0. It is a more restrictive type than any and it can help you to prevent
unintended type errors.

Unlike any , when you use the unknown type, TypeScript will not allow you to
perform any operation on a value unless you first check its type. This can help you
to catch type errors at compile-time, instead of at runtime.

For example, you can use the unknown type to create a more type-safe function:

function printValue(value: unknown) {


if (typeof value === "string") {
console.log(value);
} else {
console.log("Not a string");
}
}

You can also use the unknown type to create more type-safe variables:

let value: unknown = "hello";


let str: string = value; // Error: Type 'unknown' is not assignable to type 'st

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 8/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT

Best Practice 9: “never”


In TypeScript, never is a special type that represents values that will never occur.
It’s used to indicate that a function will not return normally, but will instead throw
an error. This is a great way to indicate to other developers (and the compiler) that a
function can’t be used in certain ways, this can help to catch potential bugs.

For example, consider the following function that throws an error if the input is less
than 0:

function divide(numerator: number, denominator: number): number {


if (denominator === 0) {
throw new Error("Cannot divide by zero");
}
return numerator / denominator;
}

Here, the function divide is declared to return a number, but if the denominator is
zero, it will throw an error. To indicate that this function will not return normally in
this case, you can use never as the return type:

function divide(numerator: number, denominator: number): number | never {


if (denominator === 0) {
throw new Error("Cannot divide by zero");
}
return numerator / denominator;
}

Best Practice 10: Using the keyof operator


The keyof operator is a powerful feature of TypeScript that allows you to create a
type that represents the keys of an object. It can be used to make it clear which
properties are allowed for an object.

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 9/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
For example, you can use the keyof operator to create a more readable and
maintainable type for an object:

interface User {
name: string;
age: number;
}

type UserKeys = keyof User; // "name" | "age"

You can also use the keyof operator to create more type-safe functions that take an
object and a key as arguments:

function getValue<T, K extends keyof T>(obj: T, key: K) {


return obj[key];
}
let user: User = { name: "John", age: 30 };
console.log(getValue(user, "name")); // "John"
console.log(getValue(user, "gender")); // Error: Argument of type '"gender"' is

Best Practice 11: Using Enums


Enums, short for enumerations, are a way to define a set of named constants in
TypeScript. They can be used to create a more readable and maintainable code, by
giving a meaningful name to a set of related values.

For example, you can use an enum to define a set of possible status values for an
order:

enum OrderStatus {
Pending,
Processing,
Shipped,
Delivered,
Cancelled
https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 10/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
}

let orderStatus: OrderStatus = OrderStatus.Pending;

Enums can also have a custom set of numeric values or strings.

enum OrderStatus {
Pending = 1,
Processing = 2,
Shipped = 3,
Delivered = 4,
Cancelled = 5
}

let orderStatus: OrderStatus = OrderStatus.Pending;

Always name an enum with the first capital letter and the name has to be in
singular form, as a part of the naming convention.

Best Practice 12: Using Namespaces


Namespaces are a way to organize your code and prevent naming collisions. They
allow you to create a container for your code, where you can define variables,
classes, functions, and interfaces.

For example, you can use a namespace to group all the code related to a specific
feature:

namespace OrderModule {
export class Order { /* … */ }
export function cancelOrder(order: Order) { /* … */ }
export function processOrder(order: Order) { /* … */ }
}
let order = new OrderModule.Order();
OrderModule.cancelOrder(order);

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 11/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
You can also use namespaces to prevent naming collisions by providing a unique
name for your code:

namespace MyCompany.MyModule {
export class MyClass { /* … */ }
}

let myClass = new MyCompany.MyModule.MyClass();

It’s important to note that namespaces are similar to modules, but they are used to
organize the code and prevent naming collisions, while modules are used to load
and execute the code.

Best Practice 13: Using Utility Types


Utility types are a built-in feature of TypeScript that provide a set of predefined
types to help you write better type-safe code. They allow you to perform common
type operations and manipulate types in a more convenient way.

For example, you can use the Pick utility type to extract a subset of properties from
an object type:

type User = { name: string, age: number, email: string };


type UserInfo = Pick<User, "name" | "email">;

You can also use the Exclude utility type to remove properties from an object type:

type User = { name: string, age: number, email: string };


type UserWithoutAge = Exclude<User, "age">;

You can use the Partial utility type to make all properties of a type optional:

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 12/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT

type User = { name: string, age: number, email: string };


type PartialUser = Partial<User>;

Best Practice 14: “Readonly” and “ReadonlyArray”


When working with data in TypeScript, you might want to make sure that certain
values can’t be changed. And that’s where Readonly and ReadonlyArray come in.

The Readonly keyword is used to make properties of an object read-only, meaning


they can’t be modified after they are created. This can be useful when working with
configuration or constant values, for example.

interface Point {
x: number;
y: number;
}
let point: Readonly<Point> = {x: 0, y: 0};
point.x = 1; // TypeScript will raise an error because "point.x" is read-only

The ReadonlyArray is similar to Readonly but for arrays. It makes an array read-only,
and it can’t be modified after it’s created.

let numbers: ReadonlyArray<number> = [1, 2, 3];


numbers.push(4); // TypeScript will raise an error because "numbers" is read-on

Best Practice 15: Type Guards


When working with complex types in TypeScript, it can be challenging to keep track
of the different possibilities of a variable. Type guards are a powerful tool that can

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 13/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
help you to narrow down the type of a variable based on certain conditions.

Here’s an example of how to use a type guard to check if a variable is a number:

function isNumber(x: any): x is number {


return typeof x === "number";
}
let value = 3;
if (isNumber(value)) {
value.toFixed(2); // TypeScript knows that "value" is a number because of the
}

Type guards can also be used with the “in” operator, the typeof operator and the
instanceof operator.

Best Practice 16: Using Generics


Generics are a powerful feature of TypeScript that allows you to write code that can
work with any type, making it more reusable. Generics allow you to write a single
function, class or interface that can work with multiple types, without having to
write separate implementations for each type.

For example, you can use a generic function to create an array of any type:

function createArray<T>(length: number, value: T): Array<T> {


let result = [];

for (let i = 0; i < length; i++) {


result[i] = value;
}

return result;
}

let names = createArray<string>(3, "Bob");


let numbers = createArray<number>(3, 0);

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 14/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
You can also use generics to create a class that can work with any type of data:

class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();


myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

Best Practice 17: Using the infer keyword


The infer keyword is a powerful feature of TypeScript that allows you to extract the
type of a variable in a type.

For example, you can use the infer keyword to create a more precise type for a
function that returns an array of a specific type:

type ArrayType<T> = T extends (infer U)[] ? U : never;


type MyArray = ArrayType<string[]>; // MyArray is of type string

You can also use the infer keyword to create more precise types for a function that
returns an object with a specific property:

type ObjectType<T> = T extends { [key: string]: infer U } ? U : never;


type MyObject = ObjectType<{ name: string, age: number }>; // MyObject is of ty

Best Practice 18: Using Conditional Types


https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 15/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
Conditional types let you to express more complex type relationships. They allow
you to create new types based on the conditions of other types.

For example, you can use a conditional type to extract the return type of a function:

type ReturnType<T> = T extends (…args: any[]) => infer R ? R : any;


type R1 = ReturnType<() => string>; // string
type R2 = ReturnType<() => void>; // void

You can also use conditional types to extract the properties of an object type that
meet a certain condition:

type PickProperties<T, U> = { [K in keyof T]: T[K] extends U ? K : never }[keyo


type P1 = PickProperties<{ a: number, b: string, c: boolean }, string | number>

Best Practice 19: Using Mapped Types


Mapped types are a way to create new types based on existing types. They allow you
to create new types by applying a set of operations to the properties of an existing
type.

For example, you can use a mapped type to create a new type that represents the
readonly version of an existing type:

type Readonly<T> = { readonly [P in keyof T]: T[P] };


let obj: { a: number, b: string } = { a: 1, b: "hello" };
let readonlyObj: Readonly<typeof obj> = { a: 1, b: "hello" };

You can also use a mapped type to create a new type that represents the optional
version of an existing type:

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 16/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT

type Optional<T> = { [P in keyof T]?: T[P] };


let obj: { a: number, b: string } = { a: 1, b: "hello" };
let optionalObj: Optional<typeof obj> = { a: 1 };

Mapped types can be used in different ways: to create new types, to add or remove
properties from an existing type, or to change the type of the properties of an
existing type.

Best Practice 20: Using Decorators


Decorators are a way to add additional functionality to a class, method or property
using a simple syntax. They are a way to enhance the behavior of a class without
modifying its implementation.

For example, you can use a decorator to add logging to a method:

function logMethod(target: any, propertyKey: string, descriptor: PropertyDescri


let originalMethod = descriptor.value;

descriptor.value = function(…args: any[]) {


console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
let result = originalMethod.apply(this, args);
console.log(`Called ${propertyKey}, result: ${result}`);
return result;
}
}

class Calculator {
@logMethod
add(x: number, y: number): number {
return x + y;
}
}

You can also use decorators to add metadata to a class, method or property, which
can be used at runtime.

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 17/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT

function setApiPath(path: string) {


return function (target: any) {
target.prototype.apiPath = path;
}
}

@setApiPath("/users")
class UserService {
// …
}
console.log(new UserService().apiPath); // "/users"

Conclusion
Whether you are a beginner or an experienced TypeScript developer, I hope that
this article has provided valuable insights and tips to help you write clean, efficient
code.

Remember, best practices are guidelines, not hard rules. Always use your own
judgment and common sense when writing code. And keep in mind that, as
TypeScript evolves and new features are introduced, the best practices may change,
so stay up to date and be open to learning new things.

We hope that you have found this article helpful and that it has inspired you to
become a better TypeScript developer. Happy coding! Don’t forget to clap,
comment, follow and subscribe, this will share TypeScript best practices with more
people and improve your karma!

Learn More

JavaScript Concepts: The Good, The Bad, and The Ugly


Dive into JavaScript concepts and discover the highs, the lows, and
the ugly truths of coding in this popular…
itnext.io

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 18/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT

The Future is Now: How AI-Driven Development will Make


Developers’ Life Easier in 2023
From tedious tasks to intelligent assistance, maximizing productivity
and minimizing errors with AI-Assisted…
itnext.io

⚛️ Building ReactVirtual Scroll in 5 min — Alternative to


Pagination and Infinite Scroll
Combining the best from pagination and infinite scroll to enhance
user experience and performance
itnext.io

Typescript JavaScript Web Development Programming

Front End Development

Get an email whenever Vitalii Shevchuk publishes.


By signing up, you will create a Medium account if you don’t already have one. Review
our Privacy Policy for more information about our privacy practices.

Subscribe

About Help Terms Privacy

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 19/20
24/04/2023, 21:02 🔥 Mastering TypeScript: 20 Best Practices for Improved Code Quality | by Vitalii Shevchuk | ITNEXT
Get the Medium app

https://itnext.io/mastering-typescript-21-best-practices-for-improved-code-quality-2f7615e1fdc3 20/20

You might also like