skip to content

In TypeScript, what does the built-in `ThisType<T>` marker do when it appears in the contextual type of an object literal, and what compiler option must be on for it to have any effect?

level: seniorimportance: nice to knowfreq 22%

answer

  1. an interface with no members
  2. a signal, not a shape
  3. contextual type of an object literal
  4. needs noImplicitThis on
  5. options-object configuration APIs

basics

~20 s

ThisType<T> is an empty marker interface. Intersected into the contextual type of an object literal, it tells the checker that this inside that literal's methods is T. It only takes effect when noImplicitThis is enabled, and it emits nothing.

solid answer

~50 s

`ThisType<T>` is declared in the standard library as an interface with no members — it carries no properties of its own and exists purely as a signal. When it is intersected into the *contextual* type of an object literal, the methods written in that literal get `T` as the type of `this` instead of the literal's own type. That is what lets an options-object API say "inside the functions you write here, `this` will be the merged state plus the other methods", which is the shape of configuration APIs that assemble an object for you and invoke your functions with a receiver they construct. Two constraints matter: it only has an effect when `noImplicitThis` is on, and it applies to the contextual type, not to a type annotation you put on the variable. Like every type, it is erased — the runtime receiver still has to be supplied by whatever code actually calls those methods.

code

typescript · 24 lines
typescript
// requires noImplicitThis (part of strict)
type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>;
};

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  const data: object = desc.data || {};
  const methods: object = desc.methods || {};
  return { ...data, ...methods } as D & M;
}

const obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx; // `this` is D & M, so the data fields are visible
      this.y += dy;
    },
  },
});

obj.moveBy(5, 5);
console.log(obj.x, obj.y); // 5 5

go deeper

for a junior

Know only that the standard library has a marker type that lets an options object's methods see a receiver assembled by the library, and that it needs strict-mode this checking to work.

for a middle

Explain that it is an empty interface acting as a signal in the contextual type of an object literal, and that intersecting it changes what this means inside that literal's methods without adding any members.

for a senior

Diagnose why it silently does nothing — noImplicitThis off, or a literal that is not contextually typed by it — and state plainly that it is a compile-time claim the implementation must honour at runtime.

for a principal

Own the API-design call: when matching an existing this-based configuration convention justifies the marker's unsoundness and flag dependency, versus passing the assembled context as an ordinary parameter so nothing has to be trusted.

## What it is `ThisType<T>` is one of the strangest entries in the standard library because it declares nothing: ```ts interface ThisType<T> {} ``` It has no members, so intersecting it with another type adds no properties and changes no assignability. Its entire purpose is to be *recognised* by the checker. When the contextual type of an object literal contains a `ThisType<T>` intersection member, the checker types `this` inside that literal's methods as `T`. ## The problem it solves A family of APIs takes a configuration object, merges the pieces, and later invokes the functions you supplied with a receiver the library assembled. The functions you write need to see that assembled receiver — not the literal you typed. Without a mechanism like this, the methods inside your literal would see only the literal's own shape, and reaching for state defined in a sibling section of the config would be an error. ```ts type ObjectDescriptor<D, M> = { data?: D; methods?: M & ThisType<D & M>; // `this` in methods is D & M }; function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M { const data: object = desc.data || {}; const methods: object = desc.methods || {}; return { ...data, ...methods } as D & M; } ``` Now a caller writes methods that freely reach both the data fields and the other methods, and the checker agrees, because inside `methods` the receiver is `D & M`. ## The two constraints candidates miss **It requires `noImplicitThis`.** With that flag off, the checker is not tracking contextual `this` types at all and the marker does nothing. This is the single most common reason someone reports that `ThisType` "doesn't work". **It works through the contextual type.** The marker must be part of the type the object literal is being checked *against* — a parameter type, a generic constraint, a declared variable type. Writing `const o: SomeType & ThisType<X> = { ... }` does work, because the annotation is the contextual type; but expecting a `ThisType` you attached elsewhere to influence a literal that is not contextually typed by it will not. ## Where it stops The marker is a claim about types only. It does not arrange for the receiver to be right at runtime — the library's own implementation must actually call the functions with the object it assembled, whether by attaching them to that object or by using an explicit call form. If the implementation gets that wrong, `ThisType<T>` will have cheerfully told every caller that `this` is something it is not. That makes it a deliberate piece of unsoundness in the same family as an assertion: you are telling the checker what to believe about a runtime arrangement it cannot verify. Second, it is not a way to type `this` in an ordinary function. A free-standing function that needs a receiver declares a `this` parameter; `ThisType<T>` is specifically about the methods of an object literal. ## Judgment: should you use it? In application code, rarely. Its natural home is library design for configuration-object APIs — the pattern where a user writes a big literal of data and behaviour and the framework wires them together. If you own both sides, the simpler design is usually to pass the assembled context as an ordinary first argument to each function, which needs no marker, no flag dependency, and no unsoundness: the type then follows from the parameter and nothing has to be trusted. The reason to reach for `ThisType<T>` is that you are matching an existing convention where users already expect `this` to be the merged object, and breaking that convention would cost more than the marker does. In an interview, the point being probed is usually whether you understand *contextual typing*: that TypeScript checks a literal against an expected type, and that expected type can carry instructions about how to check the literal's contents. `ThisType<T>` is the clearest example of that mechanism in the standard library.

  • Someone adds `ThisType<Ctx>` to a type and reports that `this` inside the literal is still the literal's own type. What do you check first?
    Whether `noImplicitThis` is enabled — the marker has no effect without it. Then whether the marker is really in the literal's *contextual* type: it must be part of the type the literal is being checked against, such as a parameter or an annotation on the variable, not something attached somewhere the literal never sees.
  • How is `ThisType<T>` different from writing a `this` parameter on each method?
    A `this` parameter is written on one function and is part of that function's own type, so it survives extraction and is checked at call sites. `ThisType<T>` is a directive in a contextual type that retypes `this` for every method of the literal at once, without appearing in any signature. They solve different problems: one constrains a function, the other configures how a literal is checked.
  • What does `ThisType<T>` guarantee about the receiver at runtime?
    Nothing. It is an empty interface used as a compile-time signal, and the entire type layer is erased. The library that accepted the literal must genuinely invoke those functions with a receiver matching `T`; if it does not, the checker was told a falsehood and the failure surfaces at runtime.

saying these in an interview costs you the question

  • Thinks ThisType<T> adds properties to the type it is intersected with
  • Expects it to work with noImplicitThis disabled
  • Believes it binds the methods at runtime
  • Tries to use it to type this in a free-standing function
  • Assumes it applies to a literal that is not contextually typed

context