Why I Automatically Reject PRs Without Strict Mode

I spent three hours last Tuesday debugging a production crash that shouldn’t have happened. The error log was screaming “Cannot read properties of undefined (reading ‘map’),” which is the JavaScript equivalent of a “Check Engine” light that’s been ignored for 50,000 miles.

The culprit? A junior dev had turned off TypeScript’s strict mode in a specific file because “the types were getting in the way.”

We’ve all been there. You just want to ship the feature. The compiler is yelling at you about potential nulls that you know won’t be null (spoiler: they will be). So you slap a // @ts-ignore or disable strict checks and move on. But here’s the thing: in 2026, writing TypeScript without Strict Mode is just writing verbose JavaScript with a false sense of security. It’s worse than useless—it’s deceptive.

I’ve stopped being polite about this in code reviews. If "strict": true isn’t enabled, or if I see any sprayed across the codebase to silence errors, I’m blocking the merge. Here is why, and more importantly, how you can actually fix your workflow so strict mode doesn’t feel like a punishment.

The “Implicit Any” Trap

The biggest offender is usually noImplicitAny. When this is off, TypeScript acts like a permissive parent who doesn’t care where you go or who you hang out with. It defaults un-typed variables to any, effectively turning off type checking entirely for those variables.

Look at this function. Without strict mode, this compiles fine:

// implicit-any is OFF
function calculateTotal(items) {
  // TypeScript infers 'items' as 'any'
  return items.reduce((acc, curr) => acc + curr.price, 0);
}

// Later in the code...
calculateTotal("oops"); // No error until runtime!
calculateTotal([{ name: "Apple" }]); // Returns NaN, no error!

This code is a time bomb. You pass a string? Crash. You pass an object without a price? NaN. The whole point of TypeScript is to catch this stuff while you’re typing, not when your user is trying to checkout.

Flip the switch to strict: true, and TypeScript immediately slaps your wrist: “Parameter ‘items’ implicitly has an ‘any’ type.” Good. It should.

TypeScript logo - TypeScript and Create-React-App. A quick how-to! | by Julia ...
TypeScript logo – TypeScript and Create-React-App. A quick how-to! | by Julia …

Here is the fix. It takes ten seconds to write but saves hours of debugging later:

interface CartItem {
  name: string;
  price: number;
}

function calculateTotal(items: CartItem[]): number {
  return items.reduce((acc, curr) => acc + curr.price, 0);
}

// calculateTotal("oops"); // Error: Argument of type 'string' is not assignable...

The DOM is a Minefield

If you do any frontend work, strictNullChecks is the specific flag inside Strict Mode that saves your life. The DOM API is notorious for returning null when you expect an element. Without strict checks, you assume the element exists. When it doesn’t? Your script dies.

I see this pattern constantly in legacy code:

// strictNullChecks is OFF
const emailInput = document.getElementById('user-email');
const submitBtn = document.querySelector('.submit-btn');

// If 'user-email' ID changed or doesn't exist on this page...
// This line throws: "Cannot read properties of null (reading 'value')"
const email = emailInput.value; 

submitBtn.addEventListener('click', () => {
    console.log(email);
});

TypeScript without strict null checks lets you access .value on emailInput because it ignores the possibility of null. It lies to you. It says, “Yeah, that’s definitely an HTMLElement,” when it’s actually HTMLElement | null.

With strict mode on, you are forced to handle reality. And honestly, the modern syntax makes handling this trivial. You don’t need five lines of if statements anymore.

// strict: true
const emailInput = document.getElementById('user-email') as HTMLInputElement | null;
const submitBtn = document.querySelector('.submit-btn');

// Option 1: Optional chaining (if you just want to avoid the crash)
const email = emailInput?.value; 

// Option 2: Guard clauses (if you need to stop execution)
if (!emailInput || !submitBtn) {
    console.warn("Required elements missing from DOM");
    return;
}

// Now TypeScript knows these are safe to use
submitBtn.addEventListener('click', () => {
    console.log(emailInput.value);
});

Async Data: Stop Trusting the API

This is where I get the most pushback. “But the API always returns that field!”

Does it? Does it really? Or did the backend team deploy a hotfix on Friday night that renamed user_id to userId without updating the docs? If you type your API responses as any or disable strict property initialization, you are flying blind.

JavaScript code on monitor - Free Photo: Close Up of JavaScript Code on Monitor Screen
JavaScript code on monitor – Free Photo: Close Up of JavaScript Code on Monitor Screen

Here is a scenario I dealt with recently. We were fetching user profiles. The code looked clean, but it was unsafe.

// The lazy way - assuming the shape matches
async function getUserData(id: string): Promise<any> {
    const res = await fetch(/api/users/${id});
    return res.json();
}

async function displayUser() {
    const user = await getUserData("123");
    // If user.settings is undefined, this throws runtime error
    document.body.innerText = Theme: ${user.settings.theme}; 
}

Strict mode forces you to acknowledge that fetch is dangerous. It encourages you to use unknown instead of any, forcing you to validate the shape before reading it. I know, validation code is boring to write. But you know what’s more boring? Fixing critical bugs at 2 AM.

Here is how a strict-mode compliant approach looks using a custom type guard. It’s verbose, yes, but it’s bulletproof.

interface UserSettings {
    theme: string;
    notifications: boolean;
}

interface User {
    id: string;
    settings?: UserSettings; // Mark as optional if it might be missing!
}

async function getUserData(id: string): Promise<User | null> {
    try {
        const res = await fetch(/api/users/${id});
        if (!res.ok) throw new Error("Failed to fetch");
        
        const data: unknown = await res.json();
        
        // Basic runtime check - in a real app, use Zod or Valibot here
        if (isValidUser(data)) {
            return data;
        }
        return null;
    } catch (e) {
        console.error(e);
        return null;
    }
}

// Type Guard
function isValidUser(data: any): data is User {
    return data && typeof data.id === 'string';
}

async function displayUser() {
    const user = await getUserData("123");
    
    // Strict mode forces us to check for null
    if (!user) return;

    // Strict mode forces us to check if settings exist
    const theme = user.settings?.theme ?? "default-blue";
    
    console.log(Applied theme: ${theme});
}

You Don’t Have to Fix It All at Once

The main reason teams avoid strict mode is the “wall of red.” You turn it on for an existing project, see 4,500 errors, and immediately turn it back off. I get it. It’s demoralizing.

JavaScript code on monitor - Learn JavaScript Fundamentals Phase 1 | Udemy
JavaScript code on monitor – Learn JavaScript Fundamentals Phase 1 | Udemy

But you don’t have to fix the whole codebase today. TypeScript allows you to migrate incrementally. My preferred strategy is using tsc --noEmit in CI to check for regressions, but only enforcing strict mode on new files or files you touch.

Better yet, use a tool to suppress existing errors. You can run a script that adds // @ts-expect-error comments to every line currently failing strict mode. This gets you to a passing state immediately. Then, as you work on files, you remove those comments and fix the types properly. It turns a massive migration into a daily maintenance task.

Final Thoughts

Writing strict TypeScript isn’t about being an elitist coder. It’s about respecting the complexity of the systems we build. JavaScript is messy, weird, and unpredictable. TypeScript’s strict mode is the only thing standing between that chaos and your production environment.

So, stop fighting the compiler. It’s the only team member that reviews your code instantly, never sleeps, and is right 99% of the time. Turn strict mode on, handle your nulls, and maybe—just maybe—you won’t have to debug undefined properties next Tuesday.

Mateo Rojas

Learn More →

Leave a Reply

Your email address will not be published. Required fields are marked *