Skip to content
TypeScript ts objects 4 min read

Excess Property Checks

TypeScript’s structural typing normally accepts any object that has at least the required properties, even if it carries extras. Yet pass an object literal with an unknown property directly to a typed slot and the compiler rejects it. This special rule is the excess property check, and it exists to catch typos and misplaced options exactly where they happen. This page explains when the check fires, why it is restricted to fresh object literals, and the safe ways to allow extra properties when you really want them.

The basic check

When you assign an object literal directly to a typed variable or pass it directly as an argument, TypeScript flags any property not declared in the target type.

interface Options {
  width: number;
  height: number;
}

// ❌ Error: Object literal may only specify known properties,
// and 'depth' does not exist in type 'Options'.
const opts: Options = { width: 10, height: 20, depth: 5 };

Under plain structural rules this object is assignable — it has width and height. But because it is a literal written inline, the extra depth is almost certainly a mistake (a typo or a property meant for a different type), so TypeScript reports it.

Why only fresh literals?

The check applies to fresh object literals — values written directly at the assignment or call site. The moment you store the literal in a variable first, it loses its freshness and ordinary structural typing takes over, so the extra property is accepted.

interface Options {
  width: number;
  height: number;
}

const raw = { width: 10, height: 20, depth: 5 };
const opts: Options = raw; // ✅ no error — `raw` is not a fresh literal

function resize(o: Options) {}
resize(raw);                          // ✅ structural: extra prop ignored
resize({ width: 1, height: 2, depth: 3 }); // ❌ fresh literal: error

This is deliberate. A literal at the call site is where typos live, so it gets extra scrutiny; an already-typed variable has presumably been vetted elsewhere. Understanding freshness explains nearly every “why does this error here but not there” question about excess properties.

SituationExcess property checked?
Literal assigned to typed variableYes
Literal passed directly as argumentYes
Variable assigned, then usedNo
Literal widened with asNo
Spread into another literalNo

Intentionally allowing extra properties

When extra keys are legitimate, you have several escape hatches. Each one tells the compiler the surplus is intended rather than a mistake.

interface Options {
  width: number;
  height: number;
}

// 1. Add an index signature for open-ended extras
interface FlexibleOptions extends Options {
  [key: string]: unknown;
}
const a: FlexibleOptions = { width: 1, height: 2, depth: 3 }; // ✅

// 2. Assign through an intermediate variable
const tmp = { width: 1, height: 2, depth: 3 };
const b: Options = tmp; // ✅

The index-signature approach is the most honest: it documents in the type that arbitrary extra keys are expected. See Index Signatures for how those behave.

Type assertions bypass the check

A type assertion (as) also suppresses the check, because it tells the compiler to trust you. Use it sparingly — it silences the very protection that would catch a typo.

interface Options {
  width: number;
  height: number;
}

// ✅ compiles, but you lose the safety net
const opts = { width: 1, height: 2, depth: 3 } as Options;

Prefer fixing the type (or the object) over asserting. An assertion that hides a real extra property is exactly the bug this feature was designed to prevent. See Type Assertion (as) for the trade-offs.

Excess checks and union types

Excess property checks behave subtly with unions: a literal is accepted only if it matches one member of the union without surplus properties relative to that member. A property valid in neither member is still rejected.

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; size: number };

const ok: Shape = { kind: "circle", radius: 5 }; // ✅

// ❌ Error: 'size' does not exist on the circle variant,
// and this does not match the square variant either.
const bad: Shape = { kind: "circle", radius: 5, size: 10 };

This keeps discriminated unions honest, preventing you from accidentally mixing fields from two variants. Discriminated unions are covered in Discriminated Unions.

Excess property checks are a feature, not an obstacle. When one fires, your first instinct should be “did I misspell a property or use the wrong type?” — reach for as only after ruling that out.

Best Practices

  • Treat an excess property error as a likely typo or a property meant for another type.
  • Remember the check only fires on fresh literals — assigning through a variable bypasses it.
  • Add an index signature when a type genuinely accepts arbitrary extra keys.
  • Avoid as to silence the check; fix the object or the type instead.
  • Define a dedicated type for objects with extra fields rather than asserting at each site.
  • With union targets, ensure the literal matches exactly one member without surplus keys.
  • Lean on this check during refactors — it flags stale option names the moment they drift.
Last updated June 29, 2026
Was this helpful?