TypeScript unknown Type
last modified October 15, 2026
The unknown type represents a value whose type is not
known yet. Like any, it accepts any value. Unlike
any, it does not allow anything to be done with the value until
its type is checked. This makes unknown the type-safe choice for
data coming from outside the program, such as user input, JSON, or caught
errors.
Assigning to unknown
Any value can be assigned to a variable of type
unknown.
let value: unknown;
value = 42;
console.log(value);
value = "falcon";
console.log(value);
value = [1, 2, 3];
console.log(value);
value = { name: "Alice" };
console.log(value);
The variable holds a number, a string, an array, and an object in
turn. Passing an unknown value to console.log is
allowed, because console.log accepts any value.
$ npx tsx assign.ts
42
falcon
[ 1, 2, 3 ]
{ name: 'Alice' }
Using an unknown value
Properties and methods of an unknown value cannot be
accessed, and the value cannot be assigned to a more specific type.
const value: unknown = "falcon"; console.log(value.toUpperCase()); const text: string = value;
The compiler rejects both lines. With any, the same code
would compile, and a wrong assumption about the value would only show up at
runtime.
$ npx tsc --noEmit use_unknown.ts(3,13): error TS18046: 'value' is of type 'unknown'. use_unknown.ts(4,7): error TS2322: Type 'unknown' is not assignable to type 'string'.
Narrowing with typeof
Before an unknown value is used, its type must be
narrowed. The typeof operator narrows primitive types.
function describe(value: unknown): string {
if (typeof value === "string") {
return `string of length ${value.length}`;
}
if (typeof value === "number") {
return `number ${value.toFixed(2)}`;
}
if (typeof value === "boolean") {
return value ? "true value" : "false value";
}
return "something else";
}
console.log(describe("falcon"));
console.log(describe(3.14159));
console.log(describe(false));
console.log(describe(null));
Inside each if block, TypeScript knows the exact type of
value, so length and toFixed can be
used.
$ npx tsx narrow_typeof.ts string of length 6 number 3.14 false value something else
Narrowing with instanceof
Objects created by classes are narrowed with the
instanceof operator.
function format(value: unknown): string {
if (value instanceof Date) {
return value.toISOString().slice(0, 10);
}
if (Array.isArray(value)) {
return `array with ${value.length} elements`;
}
return String(value);
}
console.log(format(new Date(2026, 9, 15, 12)));
console.log(format(["a", "b", "c"]));
console.log(format(100));
The instanceof Date check narrows the value to
Date. The Array.isArray function narrows it to an
array. The String function converts any value to a string.
$ npx tsx narrow_instanceof.ts 2026-10-15 array with 3 elements 100
Checking object properties
To work with objects, we check that the value is a non-null object
and that it has the expected properties. The in operator narrows
the type to an object with the given property.
function getName(value: unknown): string | undefined {
if (typeof value === "object" && value !== null && "name" in value) {
if (typeof value.name === "string") {
return value.name;
}
}
return undefined;
}
console.log(getName({ name: "Alice", age: 30 }));
console.log(getName({ name: 42 }));
console.log(getName("Alice"));
The null check is needed because
typeof null is "object". After the
in check, value.name has the type
unknown, so its type is checked as well.
$ npx tsx narrow_object.ts Alice undefined undefined
Custom type guards
Repeated checks are best moved into a type guard function. Its
return type value is User tells the compiler what a
true result means.
type User = {
name: string;
age: number;
};
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"name" in value &&
"age" in value &&
typeof value.name === "string" &&
typeof value.age === "number"
);
}
const data: unknown[] = [
{ name: "Alice", age: 30 },
{ name: "Bob" },
"Carol",
];
for (const item of data) {
if (isUser(item)) {
console.log(`${item.name} is ${item.age} years old`);
} else {
console.log("not a user:", item);
}
}
In the if branch, item has the
User type. The type guard is only as reliable as the checks
inside it; the compiler does not verify that they match the
User type.
$ npx tsx type_guard.ts
Alice is 30 years old
not a user: { name: 'Bob' }
not a user: Carol
Parsing JSON
The JSON.parse method returns any.
Assigning its result to unknown forces the data to be validated
before use.
type Product = {
id: number;
title: string;
};
function isProduct(value: unknown): value is Product {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"title" in value &&
typeof value.id === "number" &&
typeof value.title === "string"
);
}
function parseProduct(json: string): Product {
const data: unknown = JSON.parse(json);
if (!isProduct(data)) {
throw new Error("Invalid product data");
}
return data;
}
const product = parseProduct('{"id": 1, "title": "Keyboard"}');
console.log(product.title);
try {
parseProduct('{"id": "1", "name": "Mouse"}');
} catch (e) {
console.log(e instanceof Error ? e.message : e);
}
The second JSON string has a string id and no
title, so it is rejected. For larger data structures, validation
libraries such as Zod generate such checks from a schema.
$ npx tsx parse_json.ts Keyboard Invalid product data
Errors in catch blocks
In JavaScript, any value can be thrown, not only
Error objects. With the strict option, which
includes useUnknownInCatchVariables, the variable of a
catch clause has the unknown type.
function risky(kind: number): void {
if (kind === 1) {
throw new RangeError("value out of range");
}
if (kind === 2) {
throw "a plain string";
}
}
for (const kind of [1, 2]) {
try {
risky(kind);
} catch (e) {
if (e instanceof Error) {
console.log(`${e.name}: ${e.message}`);
} else {
console.log("unknown error:", e);
}
}
}
The instanceof Error check makes the
name and message properties available. The second
call throws a string, which is handled by the else branch.
$ npx tsx catch_error.ts RangeError: value out of range unknown error: a plain string
Type assertions
A type assertion converts unknown to another type
without any check. It should only be used when the type is guaranteed by
other means.
const input: unknown = "hello there"; const text = input as string; console.log(text.toUpperCase()); const wrong = 42 as unknown as string; console.log(typeof wrong);
The second assertion compiles, although the value is a number. The
compiler trusts the assertion, and the program would fail as soon as a string
method was called on wrong. Narrowing is the safer
alternative.
$ npx tsx assertion.ts HELLO THERE number
unknown in union and intersection types
Because every type is assignable to unknown, it
absorbs other types in a union. In an intersection, it disappears.
type A = unknown | string; type B = unknown & string; const a: A = 100; const b: B = "text"; console.log(typeof a, typeof b);
The type A is just unknown, so a number
can be assigned to it. The type B is string.
$ npx tsx union_unknown.ts number string
unknown versus any
Both types accept every value. The difference is in what the compiler allows afterwards.
anyturns off type checking: every property access, call, and assignment is allowed.unknownkeeps type checking on: the value must be narrowed before it is used.anycan be assigned to any other type;unknownonly tounknownandany.
Best Practices
- Prefer unknown to any: Use
unknownfor values whose type is not known in advance. - Validate External Data: Treat JSON, user input, and API responses as
unknownand check them before use. - Write Type Guards: Move repeated checks into functions with a
value is Typereturn type. - Handle Any Thrown Value: Check
e instanceof Errorincatchblocks instead of assuming anError. - Avoid Blind Assertions: An assertion such as
as Userskips all checks; narrow the value instead.
Source
This tutorial covered the TypeScript unknown type.
Use it for values of unknown origin and narrow them before use.
Author
List all TypeScript tutorials.