Now, let’s talk about one of TypeScript’s more surgical tools: NoInfer<T>. This little guy, introduced in 5.4, is for those moments when TypeScript’s type inference is a little too helpful. You know the feeling. You’ve crafted a beautifully generic function, but when you call it, TypeScript infers a type for one of your parameters that’s so spectacularly wrong it makes you question your life choices. NoInfer is your way of putting up a “Keep Out” sign on that specific parameter, telling the compiler, “I appreciate the enthusiasm, but let me handle this one.”

Think of it like this: you’re the architect, TypeScript is an overeager intern who keeps moving your furniture. NoInfer is you saying, “Don’t touch the red chair.”

The Classic Inference Ambush

The problem NoInfer solves is best shown with an example. Imagine you’re building a function to create a data fetcher. You want to specify the return type (TData) and the error type (TError), but you expect TError to be inferred from the fetcher function itself.

declare function createFetcher<TData, TError>(
  fetcher: () => Promise<TData>,
  onError: (error: TError) => void
): { fetch: () => Promise<TData> };

// The plan: TData is set to string, TError should be inferred from fetcher
const myFetcher = createFetcher<string>(
  () => Promise.resolve("Hello, world!"), // This might throw a NetworkError
  (error) => {
    // error: NetworkError 👈 This is what we WANT
    console.error(error.message);
  }
);

But here’s the rub. The type parameter TError isn’t used in the first argument (fetcher). It’s only used in the second argument (onError) and the return type. TypeScript’s inference algorithm, in its quest to be helpful, looks at the first argument, sees Promise<string>, and says, “Aha! I can infer TData from this!” But it has nothing to use to infer TError. So what does it do? It falls back to the dreaded unknown. Your error parameter in the callback is now unknown, and you’re left doing manual type checks. Thanks, TypeScript.

Enter NoInfer<T>: The Bouncer for Your Types

NoInfer<T> is a type that tells the compiler not to use a value for inferring T. It’s like wrapping a type in lead shielding. It doesn’t change the type itself; it just prevents the inference engine from peeking at it.

Here’s how we’d fix our createFetcher function. We apply NoInfer to the onError parameter. This tells TypeScript, “Don’t use this argument to infer TError. Look elsewhere.”

type NoInfer<T> = [T][T extends any ? 0 : never]; // The classic pre-5.4 hack

// Now, with TypeScript 5.4+, you get this built-in. No need to define it yourself!

declare function createFetcher<TData, TError>(
  fetcher: () => Promise<TData>,
  onError: (error: NoInfer<TError>) => void // 👈 The magic happens here
): { fetch: () => Promise<TData> };

// Let's try it again
const myFetcher = createFetcher<string, NetworkError>(
  () => Promise.resolve("Hello, world!"),
  (error) => {
    // error: NetworkError 👈 Perfect!
    console.error(error.message);
  }
);

By blocking inference on the onError callback, we’ve forced TypeScript to look for another source to infer TError. Since we’ve explicitly provided string for TData, the only place left is… the type argument we manually provided: NetworkError. Victory!

How It Works (Without the Magic)

The built-in NoInfer<T> is implemented intrinsically by the compiler, meaning it’s special-cased magic. But the community had a clever workaround for years that illustrates the concept. The common hack was:

type NoInfer<T> = [T][T extends any ? 0 : never];

This works because T extends any is always true, so the type simplifies to [T][0], which is just T. The compiler sees this seemingly pointless conditional and, in an effort to optimize, decides not to use values of this type for inference. It’s a brilliant, if slightly absurd, loophole. The official NoInfer<T> is essentially this same idea, but blessed and made reliable by the TypeScript team.

When to Wield This Power

Use NoInfer sparingly. It’s a precision tool, not a hammer. Its primary use case is exactly what we’ve seen: in generic functions where you have multiple type parameters, and you need to prevent one parameter from being inferred from a specific argument position, usually to force it to be inferred from elsewhere or explicitly provided.

A great example is the createSlice function in Redux Toolkit. It has to infer the state type from the initialState argument, but it shouldn’t infer the type of the reducers from the initialState. NoInfer is perfect for this.

The main pitfall is overusing it and making your API more confusing than necessary. If you find yourself reaching for NoInfer often, it might be a sign that your function’s generic signature could be redesigned to be more intuitive. Remember, the goal is to guide inference, not to start a fight with it.