DEV Community

Stack Horizon
Stack Horizon

Posted on

TypeScript Features Worth Adopting First

Start with the essentials, not the entire type system

When you first adopt TypeScript, the sheer number of features can be overwhelming. You don't need to master every advanced type trick on day one. In my experience, a handful of features deliver 80% of the value and make your code safer and more readable immediately. Here are the ones I'd adopt first.

1. Explicit function return types

TypeScript can infer return types, but being explicit about them acts as documentation and catches mistakes early. If a function's logic changes and the return type shifts, the compiler will tell you instead of silently breaking callers.

function getUser(id: string): User {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

This is especially useful for complex functions or those used across a codebase. It also makes code review faster because the intent is clear.

2. unknown over any

any is the escape hatch that disables type checking. unknown is the safe counterpart: you can't use it without narrowing first. When dealing with data from APIs or user input, use unknown and then narrow with type guards.

function parseJson(raw: string): unknown {
  return JSON.parse(raw);
}

const data = parseJson('{"name":"Ada"}');
if (typeof data === 'object' && data !== null && 'name' in data) {
  console.log(data.name); // safe
}
Enter fullscreen mode Exit fullscreen mode

This forces you to handle the unknown shape, preventing runtime errors.

3. Discriminated unions

When a value can be one of several shapes, a discriminated union uses a common property (the discriminant) to narrow the type. This is a clean replacement for error-prone if chains and makes exhaustive checks possible.

type Result =
  | { status: 'success'; data: string }
  | { status: 'error'; error: Error };

function handle(result: Result) {
  switch (result.status) {
    case 'success':
      console.log(result.data);
      break;
    case 'error':
      console.error(result.error.message);
      break;
  }
}
Enter fullscreen mode Exit fullscreen mode

The compiler ensures you handle every case, and you get autocompletion for the specific properties.

4. satisfies operator (TypeScript 4.9+)

satisfies lets you check that a value matches a type without changing its inferred type. This is perfect for objects that need to meet a certain shape while keeping literal types for autocompletion.

const config = {
  port: 3000,
  env: 'development',
} satisfies Record<string, string | number>;

config.port; // number, not string | number
Enter fullscreen mode Exit fullscreen mode

Without satisfies, you'd lose the specific literal types or have to use as which can hide errors.

5. Readonly and readonly properties

Immutability prevents accidental mutation bugs. Use readonly on properties and Readonly<T> for whole objects. It's a compile-time guarantee that doesn't affect runtime performance.

interface Point {
  readonly x: number;
  readonly y: number;
}

const origin: Readonly<Point> = { x: 0, y: 0 };
// origin.x = 1; // Error: Cannot assign to 'x' because it is a read-only property.
Enter fullscreen mode Exit fullscreen mode

This is especially valuable in shared code or when passing objects around.

6. Utility types: Partial, Pick, Omit

These save you from writing repetitive interfaces. Partial<T> makes all properties optional, Pick<T, K> selects a subset, and Omit<T, K> excludes some. They're perfect for update payloads or derived types.

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

type UserUpdate = Partial<Omit<User, 'id'>>;
// { name?: string; email?: string }
Enter fullscreen mode Exit fullscreen mode

They keep your types DRY and adapt when the base interface changes.

7. const assertions

as const makes a value deeply readonly and narrows literal types. It's great for configuration objects or constants that shouldn't change.

const COLORS = {
  primary: '#3498db',
  secondary: '#2ecc71',
} as const;

// COLORS.primary is '#3498db', not string
Enter fullscreen mode Exit fullscreen mode

This prevents accidental reassignment and gives you literal types for pattern matching.

8. noUncheckedIndexedAccess (strictness)

Enable this compiler option. It forces you to handle the possibility that an array index or object property access might return undefined. This eliminates a whole class of runtime errors.

const arr = [1, 2, 3];
const first = arr[0]; // number | undefined with the flag on
if (first !== undefined) {
  console.log(first.toFixed());
}
Enter fullscreen mode Exit fullscreen mode

It's a small change that surfaces real bugs during development.

Start small, then expand

These features are a solid foundation. Once you're comfortable, you can explore advanced topics like conditional types, mapped types, and template literal types. But you don't need them to benefit from TypeScript. Adopt these first, and you'll see immediate improvements in code clarity and safety without drowning in complexity.

For more details, the TypeScript Handbook is the canonical reference, and the release notes explain new features like satisfies. Happy typing!

Top comments (0)