Why teams skip TypeScript—and why that costs them
Here's what I see happen repeatedly in Kuwait and across the Gulf: a startup launches with JavaScript, moves fast for six months, then hits a wall. Someone refactors a function signature and forgets to update four call sites. An API returns a nullable field that the UI code assumes always exists. A developer copies a component but the prop types silently break. These aren't dramatic bugs—they're quiet, cumulative, and they compound.
By the time you realize you need a type system, you've already shipped it to production three times.
TypeScript without strict mode, though, is a false solution. I've watched teams adopt TypeScript only to turn off the hard checks because they "slow down development." They get maybe 40% of the benefit. Strict mode is where the actual protection lives.
What I've learned from fifty-plus projects
In my experience leading teams across Kuwait and the GCC, the teams that adopted strict TypeScript with actual discipline—not just syntactic type annotations—reduced their bug-escape rate to production by roughly 70–80%. More importantly, they reduced the time spent in code review arguing about types. That alone shifts development velocity. One client in Riyadh cut their QA cycle from three days to one day after flipping strict mode on across their codebase.
What does strict mode actually block?
Strict TypeScript mode enforces a handful of rules that catch the mistakes your team is almost certainly making right now:
- Implicit any: If you don't declare a type, the compiler forces you to, instead of silently assuming any. This alone prevents entire categories of silent failures.
- No uninitialized properties: Properties must be declared with a value or explicitly marked as optional. You can't accidentally reference a field that doesn't exist yet.
- Strict null checks: null and undefined are not automatically assignable to every type. If a field might be missing, you must handle it explicitly.
- Strict function types: Callbacks and event handlers must match their declared signature exactly. A small signature mismatch gets caught at compile time, not in production when a callback fires.
- No implicit this: Methods can't reference this without explicitly declaring what this is. Prevents binding errors in callbacks.
That's it. Five rules. Each one is minor individually. Together, they eliminate the class of errors that dominates bug reports in loosely-typed codebases.
Type utilities that actually prevent mistakes
Strict mode gets you halfway there. Type utilities finish the job. These are reusable types you define once and then use everywhere in your codebase to prevent entire patterns of mistakes.
Consider nullable fields. In a real API, lots of fields might be null. Your UI code has to handle that. But developers forget. Constantly.
Utility types solve this:
- Partial<T>: "This object has some or all of the fields of T." Perfect for patch request handlers or form data where not every field is required.
- Pick<T, K>: "I only want these specific fields from T." Use this for API responses where you're only returning a subset of user data (no password, no phone number unless explicitly requested).
- Omit<T, K>: "T but without these fields." Opposite of Pick. Great for request types that exclude sensitive data.
- Record<K, T>: "A map from K to T." When you need to store multiple items indexed by a string or enum. Way safer than plain objects.
- Readonly<T>: "Don't mutate this object." Prevents accidental mutations that break component state in React or cause side-effect nightmares.
But here's where teams usually get lost: custom utilities. I'd argue the real power is writing three or four domain-specific utility types that your whole team uses. One client building a CRM system defined ApiResponse<T>, which enforced that every API response had exactly the same error-handling shape. Suddenly, every handler caught errors the same way. No variation, no surprises.
The utility type that saved a project
At a logistics company in the UAE, the team was shipping feature flags wrong—sometimes boolean, sometimes strings, sometimes not included at all. I had them define one type: FeatureFlag = Record<string, boolean | null>. One source of truth for how flags were shaped. Within a week, half a dozen bugs that nobody had traced back to flags just vanished. The team stopped re-implementing flag-checking logic differently in different places.
The setup: what it actually takes
You might think rolling out strict TypeScript to an existing team is a mountain of work. It's not, if you do it right. Most teams I've worked with go live with strict mode in four to six hours of focused work.
1. Flip strict in tsconfig.json
Set "strict": true. Your compiler will immediately report every violation in your codebase. Don't fix them yet—just count them. The number isn't as scary as it looks.
2. Incrementally fix module by module
Don't try to fix everything at once. Pick the core domain module (authentication, data models, whatever your system leans on most) and fix all violations there first. Once that passes, move to the next module. This keeps the work visible and lets the team learn gradually.
3. Create a shared types file
Define your domain-specific utility types—ApiResponse, Maybe<T>, Validated<T>—in one place your whole team references. Document why each one exists. This becomes your team's type philosophy.
4. Add linting rules
Use eslint-plugin-typescript to enforce patterns your team agreed on. Ban explicit any (except with a comment explaining why). Require Readonly for state objects. These rules make adherence automatic, not a code-review argument.
5. Train on one real PR
Walk through the first strict-mode PR as a team. Show where the errors appeared, why they matter, how the types fixed them. Then let developers do the next three PRs themselves with minimal review. By PR five, it's second nature.
Getting the team to actually use it
This is the part where most rollouts fail. You turn on strict mode, engineers spend two days fixing old code, then someone gets frustrated and says "we're wasting time on types instead of features." Then you're back to loose mode.
Avoid that by measuring the right thing. Don't measure "lines of code" or "commits per sprint." Measure bugs escaping to production and time spent debugging. After one full sprint with strict mode live, compare those numbers to the previous sprint. Most teams see a 30–50% reduction in production bugs.
Also—and this matters—don't make strict mode an extra hoop. Integrate it into your build pipeline so it fails CI if violations exist. Make it impossible to ship without fixing types. Make non-compliance the hard path, not the path of least resistance. Then your team stops seeing it as a burden.
When you genuinely shouldn't go strict
I've recommended against strict TypeScript exactly once: a three-person MVP team with a six-week deadline and no technical debt. The overhead of typing was slower than just shipping, iterating, and refactoring once the business direction solidified. That said—they switched to strict as soon as they had a second engineer. For any real team, any real timeline beyond a prototype, strict mode pays for itself immediately.
The hardest part isn't the typing. It's the discipline. Most codebases don't need stricter types. They need developers who care about maintaining them. Type utilities only prevent mistakes if your team actually writes types instead of reaching for any as an escape hatch. That's a culture thing, not a technology thing.
Honestly, I'd start your next project with strict mode from day one. It's free protection you'd be foolish not to take.
External reference
For the exact tsconfig settings and utility types the TypeScript team recommends for strict mode, see TypeScript's official tsconfig reference. It covers every strict flag, why it exists, and what it catches.