The object Type
The lowercase object type represents any value that is not a primitive — that is, anything except string, number, boolean, symbol, null, undefined, and bigint. It matches arrays, functions, class instances, and object literals, but it tells you nothing about their shape. Because it is so unspecific, object is rarely the right annotation for data you actually read from; its best use is as a generic constraint that forbids primitives. This page distinguishes lowercase object from the confusingly similar Object, {}, and specific literal types, and shows what to use instead.
What object accepts
object admits every non-primitive value and rejects every primitive. You can assign to it freely, but you cannot access any properties off it, because no specific properties are known.
let value: object;
value = { id: 1 }; // ✅ object literal
value = [1, 2, 3]; // ✅ arrays are objects
value = () => {}; // ✅ functions are objects
value = new Date(); // ✅ class instances
// ❌ Error: Type 'string' is not assignable to type 'object'
value = "hello";
// ❌ Error: Type 'number' is not assignable to type 'object'
value = 42;
Every assignment of a non-primitive succeeds, while primitives are rejected. This “non-primitive” guarantee is the one genuinely useful property the bare object type provides.
You cannot read members off object
Since object carries no shape information, the compiler blocks property access. You must narrow first, which makes object awkward for values you intend to use.
function inspect(value: object) {
// ❌ Error: Property 'name' does not exist on type 'object'
console.log(value.name);
// ✅ Narrow first
if ("name" in value) {
console.log((value as { name: string }).name);
}
}
The in check narrows enough to know a key exists, but you still cast or refine to read it. If you need to read properties, annotate the actual shape instead of object.
object vs Object vs {}
Three look-alikes cause endless confusion. Lowercase object is “any non-primitive.” Capital Object refers to the JS Object prototype interface and accepts almost anything (primitives get boxed), so it is far too loose. The empty object type {} means “any value except null and undefined” — emphatically not “an object with no properties.”
let a: object = "x"; // ❌ Error — string is primitive
let b: {} = "x"; // ✅ allowed — {} accepts any non-nullish value
b = 42; // ✅ also allowed
// ❌ Error: Type 'null' is not assignable to type '{}'
b = null;
let c: Object = 42; // ✅ allowed (avoid: behaves like {}, accepts primitives)
{} and Object permit primitives like strings and numbers, so they are almost never what you want. When you mean “some non-primitive,” use lowercase object; when you mean a specific structure, write the explicit literal type.
| Type | Accepts | Use it for |
|---|---|---|
object | Any non-primitive (objects, arrays, functions) | Constraints that must exclude primitives |
Object | Almost anything (primitives boxed) | Avoid — too permissive |
{} | Any value except null/undefined | Avoid — means “non-nullish,” not “empty object” |
{ id: number } | Objects matching that exact shape | The values you actually read from |
Record<string, unknown> | Objects with unknown-typed string keys | Open-ended dictionaries you will narrow |
{}does not mean “an empty object.” It means “any non-null, non-undefined value,” which is nearly as broad asunknown. Reach for it almost never; preferRecord<string, unknown>orunknownwhen you intend “some value.”
Prefer specific shapes
For data you read, describe the structure with an object literal type or interface. For open-ended dictionaries, Record<string, unknown> is far safer than object because you can index it.
// ✅ Precise shape — full autocomplete and checking
type User = { id: number; name: string };
// ✅ Open-ended dictionary you will narrow per-key
function logEntries(data: Record<string, unknown>) {
for (const [key, val] of Object.entries(data)) {
console.log(key, val);
}
}
A named shape gives you property access, autocomplete, and refactoring safety. Record<string, unknown> keeps things open while still letting you index and forcing you to narrow each value — strictly better than bare object for dynamic data.
object as a generic constraint
The one place lowercase object earns its keep is constraining a type parameter to reject primitives — for example, a helper that should only operate on reference types.
function freeze<T extends object>(value: T): Readonly<T> {
return Object.freeze(value);
}
freeze({ a: 1 }); // ✅ T = { a: 1 }
freeze([1, 2]); // ✅ T = number[]
// ❌ Error: Argument of type 'number' is not assignable to parameter of type 'object'
freeze(42);
T extends object lets the function accept any object-like value while preserving its specific type T, and it cleanly rejects primitives that Object.freeze would treat as no-ops. This constraint role is the idiomatic use of object.
Best Practices
- Annotate the actual shape (
{ ... }or an interface) for any value you plan to read from. - Use lowercase
objectmainly as a generic constraint (<T extends object>) to exclude primitives. - Prefer
Record<string, unknown>overobjectfor open-ended dictionaries you will narrow. - Avoid
{}andObjectas annotations — both accept primitives and rarely express intent. - Remember
{}means “any non-nullish value,” not “an object with no properties.” - Narrow with
inor a type guard before accessing properties on anobject-typed value. - When you truly mean “any value,” use
unknownrather thanobject,{}, orany.