Back to blog

TypeScript Best Practices for Modern Web Development

3 min readBy Mustafa Akkaya
#TypeScript#JavaScript#Best Practices

TypeScript has become the standard for building scalable web applications. Let's explore the best practices that will make your TypeScript code more robust and maintainable.

Why TypeScript Matters

TypeScript brings:

- Type Safety - Catch errors before runtime

- Better IDE Support - Enhanced autocomplete and refactoring

- Self-Documenting Code - Types serve as inline documentation

- Confidence in Refactoring - Compiler catches breaking changes

- Enterprise Ready - Scales well for large codebases

Essential Best Practices

1. Use Strict Mode

Always enable strict mode in your tsconfig.json:

json

{

"compilerOptions": {

"strict": true,

"noUncheckedIndexedAccess": true,

"noImplicitOverride": true

}

}

2. Prefer Interfaces Over Type Aliases

For object shapes, interfaces are more extensible:

typescript

// Good

interface User {

id: string;

name: string;

email: string;

}

// For unions and primitives, use type

type Status = "active" | "inactive" | "pending";

3. Use Unknown Instead of Any

Unknown is type-safe and forces you to check types:

typescript

// Avoid

function process(data: any) {

return data.value;

}

// Better

function process(data: unknown) {

if (typeof data === "object" && data !== null && "value" in data) {

return (data as { value: string }).value;

}

}

4. Leverage Type Guards

Create custom type guards for complex checks:

typescript

interface APIResponse {

success: boolean;

data?: unknown;

error?: string;

}

function isSuccessResponse(

response: APIResponse

): response is APIResponse & { data: unknown } {

return response.success && response.data !== undefined;

}

5. Use Const Assertions

For immutable values and literal types:

typescript

const routes = {

home: "/",

about: "/about",

contact: "/contact",

} as const;

type Route = (typeof routes)[keyof typeof routes];

Advanced Patterns

Generic Constraints

Make generics more specific:

typescript

function getProperty(obj: T, key: K): T[K] {

return obj[key];

}

Utility Types

Leverage built-in utility types:

typescript

type PartialUser = Partial;

type ReadonlyUser = Readonly;

type UserWithoutId = Omit;

type UserPickedFields = Pick;

Discriminated Unions

For type-safe state management:

typescript

type LoadingState = { status: "loading" };

type SuccessState = { status: "success"; data: string };

type ErrorState = { status: "error"; error: Error };

type State = LoadingState | SuccessState | ErrorState;

function handleState(state: State) {

switch (state.status) {

case "loading":

// TypeScript knows no data/error here

break;

case "success":

// TypeScript knows data exists

console.log(state.data);

break;

case "error":

// TypeScript knows error exists

console.error(state.error);

break;

}

}

Common Pitfalls to Avoid

- Don't use any - Use unknown or proper types

- Avoid type assertions - Use type guards instead

- Don't ignore errors - Configure strict mode

- Avoid deep nesting - Extract complex types

- Don't over-engineer - Keep types simple and readable

Tools and Resources

- TSConfig Cheat Sheet - Understand compiler options

- TypeScript Playground - Test and share code snippets

- ESLint TypeScript Plugin - Catch additional issues

- ts-reset - Improve built-in type definitions

Conclusion

TypeScript is a powerful tool that improves code quality and developer experience. By following these best practices, you'll write more maintainable and reliable applications.

Remember: the goal is not to fight TypeScript, but to let it help you write better code.

Happy typing! 💪