10 Typescript power ups 🍄

you whish you knew before

1: satisfies operator

// âś… Good (satisfies operator)
const theme = {
  primary: "#ff6600",
  spacing: 8,
} satisfies Record<string, string | number>;

// ❌ Bad (interface or type assertion)
interface Theme {
  primary: string;
  spacing: number;
}
const theme: Theme = { 
  primary: "#ff6600", 
  spacing: 8 
}; // literals widened
Use satisfies when you want to enforce a shape while preserving literal types—perfect for tokens, settings, or config.
4.5 the-satisfies-operator

2: String-literal union from array

// âś… Good (string-literal union from array)
const routes = ["home", "blog", "about"] as const;
type Route = typeof routes[number]; 
// "home" | "blog" | "about"

// ❌ Bad (enum or plain string)
enum Route { 
  Home = "home", 
  Blog = "blog", 
  About = "about" 
}
Use for dynamic route guards, tabs, CSS variants—any time enum is too heavy but you want strong inference.
3.4 const-assertions

3: Template literal types

// âś… Good (template literal types)
type Version = "v1" | "v2";
type Resource = "users" | "posts";
type Endpoint = `/${Version}/${Resource}`;

// ❌ Bad (string concat)
const url = "/" + "v1" + "/" + "users";
type Endpoint = string;
Enforce patterns for REST routes, i18n keys, CSS naming, and more.
4.1 template-literal-types

4: unknown instead of any

// âś… Good (unknown instead of any)
function parseJson<T>(text: string): T {
  return JSON.parse(text) as unknown as T;
}
// ❌ Bad (any)
function parseJson(text: string): any {
  return JSON.parse(text);
}
Use unknown to avoid unsafe property access on dynamic/external data.
3.0 unknown-type

5: infer helper

// âś… Good (infer helper)
type ElementType<T> = T extends (infer U)[] ? U : never;
type Values = ElementType<[1, 2, 3]>; 
// 1 | 2 | 3

// ❌ Bad (naive index type)
type ElementType<T> = T[number]; // breaks for non-array
Extract inner types from tuples, promises, responses—great for generics.
2.8 conditional-types-infer

6: asserts type guard

// âś… Good (asserts type guard)
function assertInput(el: HTMLElement | null): 
  asserts el is HTMLInputElement {
  if (!(el instanceof HTMLInputElement)) 
    throw new Error("Not an input");
}
const node = document.querySelector("#email");
assertInput(node);
node.value = "hello@example.com";

// ❌ Bad (as casting)
const node = document.querySelector("#email") 
  as HTMLInputElement;
node.value = "hello@example.com";
Write safer DOM or test utility code without fragile casting.
3.7 assertion-functions

7: const type parameters

// âś… Good (TS 5.x const type parameters)
function defineRoutes<const R extends readonly string[]>
  (routes: R) {
  return routes;
}
const routes = defineRoutes(["/users", "/posts"]);
type Route = typeof routes[number];

// ❌ Bad (manual as const)
const routes = ["/users", "/posts"] as const;
Factory APIs can preserve literal types automatically—no as const needed.
5.0 const-type-parameters

8: import type

// âś… Good (import type)
import type { User } from "./models";

//❌ Bad (standard import)
import { User } from "./models";
Reduces bundle size and speeds builds—tree-shake safely.
3.8 type-only-imports-and-export

9: exhaustive switch with never

// âś… Good (exhaustive switch with never)
type Shape = { kind: "circle"; r: number } | 
             { kind: "square"; s: number };
function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle": return Math.PI * shape.r ** 2;
    case "square": return shape.s ** 2;
    default:
      const _exhaustive: never = shape;
      return _exhaustive;
  }
}

// ❌ Bad (forget a case)
function area(shape: Shape): number {
  if (shape.kind === "circle") 
    return Math.PI * shape.r ** 2;
  return 0;
}
Ensure full coverage of union types—especially when adding new variants.
2.0 exhaustiveness-checking

10: project references + incremental build

//âś… Good (project references + incremental build) 
// packages/lib/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "outDir": "dist"
  }
}
/* root tsconfig.json */
{ "files": [], "references": [{ "path": "./packages/lib" }] }

npx tsc -b          # initial build
npx tsc -b --incremental  # incremental

// ❌ Bad (monolithic rebuild)
npx tsc --project .
Optimize CI & local build speed in large repos or monorepos.
3.0 project-references

BONUS: version ranges + lock files


// âś… Good (version ranges + lock files)
{
  "dependencies": {
    "express": "^4.18.2", // agile upgrades
    "lodash": "~4.17.21", // safe patches
		"react": "18.2.0" // exact pin
  }
}

// ❌ Bad (loose ranges)
{
  "dependencies": {
    "express": "*", // any version
    "lodash": ">=4.0.0" // will take major bumps
  }
}
  • Use ^ for devDependencies (agile upgrades)
  • Use ~ for prodDeps (safe patches)
  • Use exact pins or lock file in passive codebases

Experiment / POC

  • Use ^ to get the latest fixes & features automatically.
  • Skip locking during rapid prototyping—but snapshot a lock before demos.

Active, Long‑Running

  • Rely on ~ for production deps to limit risk to critical patches.
  • Dev‑only tools (linters, build) can use ^ for faster upgrades.
  • Automate weekly or monthly npm update --depth 0 in CI to catch minor bumps.

Passive, Long‑Running

  • Pin exact versions to prevent any drift.
  • Treat dependency updates as deliberate events—bundle them in dedicated maintenance sprints.
  • Keep your lock file under version control; don’t let automated bots merge dependency PRs without review.

Thanks!