Inference picks the type a value could change to, not the exact value it holds. Inside an object, method: "GET" is inferred as string, because object properties can be reassigned, so the object no longer fits a parameter that only accepts "GET" | "POST". The value is exactly right; the inferred type is too wide, and the error points at the call, not at where the width came from.
type Method = "GET" | "POST";
function send(url: string, method: Method) {
console.log(method, url);
}
const request = { url: "/api/users", method: "GET" }; // method inferred as string
send(request.url, request.method);
// error TS2345: Argument of type 'string' is not assignable to parameter of type 'Method'.TypeScript infers a variable's type from its initializer, a function's return type from its return statements, and can even infer a parameter's type from the expected type of the position it's used in ("contextual typing"). This means idiomatic TypeScript code annotates far less than newcomers expect — explicit types are reserved for function parameters (which cannot be inferred from nothing) and cases where inference genuinely can't determine the intended type.
Inference from initializers
None of these need an explicit type annotation at all — TypeScript reads the initializer and infers the precise type automatically.
let userName = "Alice"; // inferred: string
let age = 30; // inferred: number
let scores = [90, 85, 78]; // inferred: number[]
// userName = 42; // COMPILE ERROR: even without an explicit annotation, the inferred type still appliesThat's the same inference as let userName = "Alice" becoming string above: anything that can be reassigned is widened to its general type. A top-level const method = "GET" would keep the literal type, but a property of a const object can still change. as const tells TypeScript the whole object is read-only, so every property keeps its literal type:
const request = { url: "/api/users", method: "GET" } as const; // method: "GET"
send(request.url, request.method); // GET /api/usersReturn type inference
The return type is inferred from every return statement in the function, unioned together if they differ — an explicit annotation is still good practice for public API functions, since it documents intent and catches an accidental wrong return immediately, even though it's not required.
function double(x: number) { // return type inferred, not annotated
return x * 2; // TypeScript infers: number
}
function maybeDouble(x: number, shouldDouble: boolean) {
if (shouldDouble) return x * 2;
return undefined;
} // inferred return type: number | undefinedContextual typing: inferring parameter types from usage
Because Array.prototype.map's signature expects a callback of a known shape, TypeScript can infer n's type as number from context, with zero annotation needed inside the callback itself.
const numbers = [1, 2, 3];
numbers.map((n) => n * 2);
// n's type is inferred as `number` from the array's element type —
// no annotation needed on n at all