TypeScript: The Good, The Bad, and The Advanced

Serhii Koziy
4 min readFeb 8, 2025

As a Senior Frontend Developer, embracing TypeScript is almost inevitable in modern web development. It brings type safety, better tooling, and improved maintainability. However, it’s not without its drawbacks and potential pitfalls. In this article, I’ll dive into TypeScript’s pros and cons, common bad practices, and an advanced example of implementing a Partial<T> type.

The Pluses of TypeScript

  1. Type Safety: TypeScript ensures variables and function return types match expected types, reducing runtime errors.
  2. Improved Developer Experience: With IDE support like VS Code, developers get autocompletion, inline documentation, and error highlighting.
  3. Better Code Maintainability: Large codebases benefit significantly from TypeScript’s explicit types and interfaces, making refactoring safer.
  4. More Readable Code: Properly typed code is often self-documenting, making it easier for teams to understand.
  5. Enhanced JavaScript Features: TypeScript includes features like enums, interfaces, and generics, which aren’t available natively in JavaScript.
  6. Great Ecosystem Support: Many modern libraries and frameworks (React, Angular, Vue) offer first-class TypeScript support.

The Minuses of TypeScript

  1. Learning Curve: While JavaScript developers can quickly get started, mastering TypeScript’s advanced features requires time.
  2. Compilation Overhead: Unlike JavaScript, TypeScript requires a compilation step, which can slow down development.
  3. Verbose Syntax: TypeScript’s explicit typing can sometimes make code more cluttered than vanilla JavaScript.
  4. Complexity in Large Projects: Overuse of types and generics can lead to over-engineering and unnecessary complexity.
  5. Third-Party Typings: Not all JavaScript libraries provide official TypeScript support, leading to reliance on DefinitelyTyped (@types/* packages) or custom type definitions.

Bad Practices and Anti-Patterns

1. Overusing any

While any can be a quick fix, it defeats TypeScript's purpose by removing type safety.

function fetchData(): any {  // Bad practice
return fetch("/api/data").then(res => res.json());
}

Fix: Use proper types or generics.

async function fetchData(): Promise<MyDataType> {
const response = await fetch("/api/data");
return response.json();
}

2. Ignoring Type Inference

Explicitly defining types that TypeScript can infer is unnecessary.

const age: number = 30;  // Redundant

Better:

const age = 30;  // TypeScript infers it as number

3. Using null Instead of undefined

JavaScript treats null and undefined differently, and mixing them leads to inconsistencies. TypeScript favors undefined.

type User = {
name: string;
age?: number | null; // Bad practice
};

Fix: Prefer undefined for optional values.

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

4. Excessive Use of ! (Non-Null Assertion Operator)

Using ! excessively can lead to runtime errors.

const user: User = getUser();
console.log(user.name!); // What if user is null?

Better:

if (user) {
console.log(user.name);
}

Union and Intersection Types

Union Types

Union types allow a variable to hold multiple possible types.

type Status = "success" | "error" | "loading";

function printStatus(status: Status): void {
console.log(`Current status: ${status}`);
}

Intersection Types

Intersection types combine multiple types into one.

type Person = { name: string };
type Employee = { company: string };
type EmployeePerson = Person & Employee;

const employee: EmployeePerson = { name: "Alice", company: "TechCorp" };

Keyof, Typeof, and Conditional Types

keyof Operator

Extracts all keys from a given type.

type User = { id: number; name: string };
type UserKeys = keyof User; // "id" | "name"

typeof Operator

Used to infer types from existing variables.

const user = { id: 1, name: "Alice" };
type UserType = typeof user; // { id: number; name: string; }

Conditional Types

Used to create dynamic types based on conditions.

type IsString<T> = T extends string ? "Yes" : "No";
type Test = IsString<number>; // "No"

Utility Types

Partial<T>

Makes all properties of T optional.

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

Required<T>

Makes all properties of T required.

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

Readonly<T>

Makes all properties of T readonly.

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

Pick<T, K>

Creates a new type by picking specific keys from T.

type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

Omit<T, K>

Creates a new type by omitting specific keys from T.

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

Implementing Partial<T> in TypeScript

The Partial<T> utility type is a built-in TypeScript helper that makes all properties of a given type T optional. This is particularly useful when dealing with functions that update an object, where only a subset of properties needs to be modified.

Implementation of Partial<T>

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

Explanation:

  • T is a generic type representing any object.
  • keyof T extracts all property keys from T.
  • The mapped type [P in keyof T] iterates over each key P in T.
  • T[P] represents the type of each property.
  • The ? makes each property optional.

Usage Example:

interface User {
id: number;
name: string;
email: string;
}

const updateUser = (id: number, userUpdate: Partial<User>) => {
console.log(`Updating user ${id} with`, userUpdate);
};
// Valid usage:
updateUser(1, { name: "Alice" });
updateUser(2, { email: "newemail@example.com" });
updateUser(3, {}); // Even an empty object is valid

Why Use Partial<T>?

  • Reduces the need to define multiple interfaces for partial updates.
  • Prevents unnecessary properties from being required when updating an object.
  • Makes function parameters more flexible without sacrificing type safety.

By leveraging Partial<T>, developers can write more maintainable and flexible code, particularly when working with APIs and state management.

Conclusion

TypeScript is a powerful tool that significantly improves JavaScript development when used correctly. However, misusing it can introduce complexity and reduce its benefits. By avoiding common pitfalls, leveraging union and intersection types, and using advanced features like keyof, typeof, conditional types, and utility types, developers can write more robust, maintainable, and readable code.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response