When inferring types, TypeScript often errs on the more general side. This may sound counter-intuitive as the existence of TypeScript is based on type safety, which requires TypeScript to be as precise and strict as possible. However, this feature proves to be necessary and extremely useful. It is called Type Widening, a key concept to TypeScriptโ€™s type inference mechanism.

For example, in TypeScript, an absence of something can be represented by either null or undefined. Imagine a situation where a variable has to be declared before its final value is known:

// When --strictNullChecks: true
let a = null; // type `null`, unable to assign any other value to it anymore
let b = undefined; // type `undefined`
 
// With --strictNullChecks: false
let a = null; // type `any`
              //A non-absentia value can be assigned to it at a later stage
const b = null; // type `null`

Without Type Widening, as shown in the first half of the example, we would not be able to assign a meaningful value to the variable a at a later stage, rendering it less useful.

Type Widening for Type Literals and Object Literals

The same consideration applies to type literals, including primary type literals and object literals. The following code demonstrates how it works with primary type literals:

// Declared as `const`, no widening happens
const num = 2; // type 2;
const name = "Tolu"; // type 'Tolu'
const isTrue = true; // type `true`
 
// Declared as `var`, TpyeScript tries to be helpful by widening the types
let surname = "Agboola"; // type `string`
let num2 = 10; // type `number`
let bool = true; // type `boolean`
 
// You can use the no-widening literal types to escape from being widened
let narrowedName: "Tolu" = name; // narrowed to type 'Tolu';
let life42: true = true // type `true`

With object literals, we introduce a concept called fresh object literal type, referring to the type TypeScript infers from an object literal. The rule states that if an object literal uses a type assertion or is assigned to a variable, it will be widened to a regular object type and is no longer considered fresh.

This is the base of excess property checking, where TypeScript reports an error when you try to assign a fresh object literal type T to another type U and T has extra properties that are not present in U.

type Options = {
  baseURL: string,
  cacheSize?: number,
  tier?: 'prod' | 'dev' 
};
  
class API {
  constructor(private options: Options) {
    throw Error(โ€œNot implementedโ€);
  }
} 
 
new API({ 
  baseURL: 'https://api.mysite.com',
  tier: 'prod' 
}); // OK!
 
new API({
  baseURL: 'https://api.mysite.com',
  badTier: prod',
});
// Error TS2345: Argument of type '{baseURL: string; badTier: string}' 
// is not assignable to the parameter of type 'Options'.
// Fresh object literal type - excessive property check kicks in
// In reality, this is almost always a typo
 
new API({
  baseURL: 'https://api.mysite.com',
  badTier: 'prod' 
} as Options); // OK!
// Explicit type assertion, no longer considered fresh by TypeSceript,
// Hence no excessive properties check
 
let badOptions = {
  baseURL: 'https://api.mysite.com', 
  badTier: 'prod',
};
// Assigned to a variable - no longer considered fresh by TypeSceript,
//Hence no excessive properties check
 
new API(badOptions); // OK!
 
let options: Options = {
  baseURL: 'https://api.mysite.com',
  badTier: 'prod',
}
// Error TS2322: Type '{baseURL: string; badTier: string}' 
// is not assignable to type 'Options'.
// Explicit typing `options` as `Options`is a hint to TypeScript to
// check the type for us (maintain the freshness)
 
new API(options); // Error!