Skip to content
Node-devs logo

Blog

TypeScript Best Practices: 10 Habits for Cleaner, Safer Code

Ten TypeScript habits that make code cleaner and safer: strict mode, unknown instead of any, discriminated unions, runtime validation and automated checks.

Rafiq Hasan 4 min read
Share
TypeScript best practices - code window with brackets
Table of contents
  1. Key takeaways
  2. 1. Turn On Strict Mode
  3. 2. Avoid any, Prefer unknown
  4. 3. Let Inference Work
  5. 4. Model State With Discriminated Unions
  6. 5. Check Exhaustively
  7. 6. Narrow Instead of Asserting
  8. 7. Validate Data at the Edges
  9. 8. Use Utility Types and Derive Types From Values
  10. 9. Prefer Immutability and Keep Types Close
  11. 10. Enforce It Automatically
  12. Common Mistakes
  13. Keep Improving
  14. References and further reading

Adopting TypeScript is step one. Using it well is where the real benefits appear. If you are still weighing the switch, start with TypeScript vs JavaScript. Already using it? We combined the official TypeScript documentation, the typescript-eslint project and two practitioner checklists into ten habits that consistently show up.

Key takeaways

  • Turn on strict, avoid any, and validate external data at runtime.
  • Model state with discriminated unions and let the compiler check you have handled every case.
  • Let inference do the easy work, and enforce the rules automatically in CI.

1. Turn On Strict Mode

The TypeScript reference explains that strict enables a family of checks at once: noImplicitAny, strictNullChecks, strictFunctionTypes, strictPropertyInitialization, useUnknownInCatchVariables and more. It is marked as recommended for new projects, with one caveat: future TypeScript versions may add new checks under strict, so upgrades can surface new errors. Practitioner lists add two optional flags worth considering: noUncheckedIndexedAccess (indexed reads include undefined) and exactOptionalPropertyTypes, which catches bugs strict mode alone misses.

2. Avoid any, Prefer unknown

typescript-eslint explains why: any is an escape hatch that disables most type checking for the values it touches, and because it spreads through code, other type-aware rules have nothing to check. When you truly do not know a type, use unknown, which forces you to narrow it before use. When you do know the shape, describe it with an interface or type; for several possibilities use a union; for arbitrary keys use an index signature. The same documentation allows any as a last resort, for instance while migrating or when a package lacks types, and recommends a targeted disable comment rather than turning the rule off.

3. Let Inference Work

The handbook's advice is to start with fewer annotations. TypeScript usually infers variable types from initial values and often infers return types too. Redundant annotations can widen types and add upkeep. Add explicit types at boundaries: function parameters, exported APIs and public return types.

4. Model State With Discriminated Unions

Instead of several optional fields or boolean flags, use a union with a literal tag, such as a request that is loading, error (with a message) or success (with data). Invalid combinations become impossible to write. Both practitioner lists recommend this over loose flags.

5. Check Exhaustively

In a switch over a union, add a default branch that assigns the value to type never. If someone adds a new member to the union later, the compiler flags every place that has not handled it.

6. Narrow Instead of Asserting

The handbook shows how typeof, Array.isArray and equality checks narrow a type inside a branch. A type assertion (as T) just tells the compiler to trust you and has no runtime effect, so use it sparingly. Both checklists also advise avoiding the non-null assertion (!) to skip checks. Prefer satisfies over as when you only want to check a value against a type while keeping its precise inferred type, and write type predicates (value is T) for checks that need to narrow at runtime.

7. Validate Data at the Edges

This is the biggest gap in static typing: TypeScript cannot validate external data. Casting response.json() with as User compiles but can still fail at runtime. API responses, form input, environment variables and JSON files need runtime checks. Validate at the boundary with a schema library such as Zod (or hand-written type guards), then let types carry the validated data inward. Infer the TypeScript type from the schema so the two never drift apart.

8. Use Utility Types and Derive Types From Values

Pick, Omit, Partial, Required and indexed access types derive new types from existing ones instead of duplicating them. Building a union from a runtime as const array keeps the list and the type in sync, and literal unions are often easier to refactor and serialise than enums.

9. Prefer Immutability and Keep Types Close

Mark data that should not change as readonly and use as const for fixed lists and configuration so values stay literal. Hyring's checklist also recommends colocating types with the code that uses them, rather than one giant "types" file, and keeping functions small and single-purpose so their types stay readable. Naming conventions help too: PascalCase for types, camelCase for values, and boolean names starting with is, has or should.

10. Enforce It Automatically

Run tsc --noEmit and a linter on every pull request, with rules that flag explicit any and floating promises. A type error should block a merge just like a failing test. This fits neatly into a workflow built with Git and GitHub.

Common Mistakes

  • Sprinkling // @ts-ignore or as any instead of fixing the problem.
  • Over-engineering with complex generic types nobody can read.
  • Trusting types for data that never passed validation.
  • Adopting TypeScript without strict, which keeps autocomplete but misses most of the safety.

Keep Improving

Great TypeScript is readable first and clever second. If you want an experienced team to set up a typed, tested and maintainable codebase, Node-devs can help you build it right. Our Node.js backend guide shows where these habits matter most.

References and further reading

This guide was compiled from the sources below. We compared them, kept the points they agree on and wrote the article in our own words. Check the originals for more detail.

  1. TSConfig reference: strict (TypeScript documentation)
  2. Everyday Types (TypeScript Handbook)
  3. no-explicit-any (typescript-eslint)
  4. TypeScript tips everyone should know (AllThingsSmitty (GitHub))
  5. TypeScript best practices (Hyring)

Frequently asked questions

Why should I avoid the any type?+

any turns off type checking for the values it touches, so mistakes only appear at runtime, and it spreads through code so other type-aware lint rules have nothing to check. Use a real type, a union or unknown instead.

What does TypeScript strict mode do?+

It enables a family of stricter checks together, such as noImplicitAny, strictNullChecks and strictFunctionTypes. The official reference recommends it for new projects.

Do TypeScript types replace validation?+

No. Types are erased at runtime, so data from users, APIs, files and environment variables must be validated with a runtime check or schema library at the boundary.

When is any acceptable?+

typescript-eslint says it is a last resort, sometimes reasonable while adopting TypeScript, for a package that lacks types or for code too complex to type. Use a targeted lint-disable comment with an explanation rather than switching the rule off.

Rafiq Hasan

Written by

Rafiq Hasan

Full-Stack Developer

Rafiq writes practical guides on Node.js, TypeScript and shipping software, with a focus on developers starting out in South Asia.

Related Articles

Get practical tech insights, monthly

One email a month with our best guides on software, AI and growth. No spam - unsubscribe any time.

Contact Our Team

We pride ourselves staying at the front of innovation, constantly pushing boundaries - shaping the Digital World Together and redefining what's possible.

To More Inquiry

+8801867748073

To Send Mail

info@node-devs.com

Your Success Starts Here!

Protected by reCAPTCHA & honeypot spam filtering.

TypeScript Best Practices: 10 Habits for Safer Code