Typorithm · medium

15. Type regression tests and deliberate strictness

typescriptstable-typescriptinterviewindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

15. Type regression tests and deliberate strictness

0%

Concept and contract

Test both valid calls and rejected contracts without mistaking diagnostic suppression for correctness.

A type regression test checks an API’s accepted and rejected uses. A positive assignment proves one useful call still compiles. A negative fixture uses @ts-expect-error immediately before a deliberately invalid statement so the compiler reports a failure if that statement stops producing any diagnostic.

An expected-error directive does not require a particular diagnostic code or message. Keep the invalid statement small enough that an unrelated error cannot accidentally satisfy the test. For critical diagnostics, inspect or assert the relevant compiler output in a dedicated fixture.

Mechanism and worked example

The example uses a constrained update function whose key determines the permitted value.

The example uses a constrained update function whose key determines the permitted value. A positive call updates count with a number. The rejected call supplies text for the same property. Together they test the relationship that would be lost if the implementation’s signature changed to any.

Keep strict and additional options explicit. noUncheckedIndexedAccess and exactOptionalPropertyTypes cover different risks; noImplicitOverride and noFallthroughCasesInSwitch add further targeted checks. Enable options intentionally and explain the contract each one protects instead of treating every flag as interchangeable strictness.

Complete worked example

typescript

function update<T, K extends keyof T>(value: T, key: K, next: T[K]): T {
  return { ...value, [key]: next };
}
const changed = update({ count: 1, title: "QA" }, "count", 2);
Reasoning
K and T[K] connect the selected property with its replacement type.

Counterexamples and common mistakes

@ts-ignore differs from @ts-expect-error because it does not complain when the following statement becomes valid.

@ts-ignore differs from @ts-expect-error because it does not complain when the following statement becomes valid. That makes it weaker for negative regression tests. Neither directive performs runtime validation or guarantees that the remaining program follows its intended business rules.

A failing negative fixture can mean the API became too broad, but it can also mean the test accidentally changed. Read the failure before deleting the directive. Avoid hundreds of suppressed lines, because unrelated diagnostics make the original contract difficult to identify.

Rejected counterexample: expected compiler diagnostic

typescript

function update<T, K extends keyof T>(value: T, key: K, next: T[K]): T { return { ...value, [key]: next }; }
// @ts-expect-error count requires a number.
update({ count: 1 }, "count", "two");
Reasoning
The directive checks that this misuse remains rejected; it is not an implementation workaround.

Independent practice

Implement setFlag with a finite key union and boolean values.

Implement setFlag with a finite key union and boolean values. Write two valid uses and two invalid uses: an unknown key and a nonboolean value. Keep each invalid statement on its own line under its own expected-error directive.

Also run a runtime check showing that the intended flag actually changes and an unrelated flag does not. The exercise combines signature regression with observable behavior. It does not assume that a type-level test alone proves correct property assignment.

Step 1: Define the finite flag-key domain. Step 2: Return a new record for one boolean update.

Practice starter: solve before reading the solution

typescript

type Flags = { remote: boolean; saved: boolean };
function setFlag(flags: Flags, key: keyof Flags, value: boolean): Flags { throw new Error("TODO"); }
Reasoning
Test unknown keys and wrong value types as distinct negative statements.
  • Hint 1: Use separate negative lines for unknown key and wrong value.
  • Hint 2: Do not execute rejected calls.

Solution, tests and trade-offs

The solution’s finite key union protects the vocabulary while the boolean parameter protects the value domain.

The solution’s finite key union protects the vocabulary while the boolean parameter protects the value domain. Its negative tests isolate the two rejected dimensions. A generic version could preserve a richer key-value mapping, but that extra abstraction is unnecessary for two uniform boolean flags.

Compile with noEmit first, then run a separate executable positive fixture. Never execute rejected examples merely because the compiler accepted their expected-error directives: suppressed diagnostics do not make invalid calls safe to run.

Practice solution and acceptance checks

typescript

type Flags = { remote: boolean; saved: boolean };
function setFlag(flags: Flags, key: keyof Flags, value: boolean): Flags {
  return { ...flags, [key]: value };
}

// Acceptance checks
const input={remote:false,saved:true};const result=setFlag(input,"remote",true);if(!result.remote||!result.saved||input.remote)throw new Error("flags");
Reasoning
A finite key domain is sufficient when every selected value has the same boolean type.

Interview reasoning and next step

In an interview, describe a type test that would detect an accidental any and a runtime test that would detect writing the wrong field.

In an interview, describe a type test that would detect an accidental any and a runtime test that would detect writing the wrong field. Explain why the two tests are complementary and why expected-error directives should remain close to tiny negative examples.

The foundations capstone combines these techniques with validation, unions, readonly inputs and generic selection. Treat it as an independent implementation review rather than another syntax recall exercise.

Optional stretch — after the core task works: Add a type regression that would detect accidental any.

Test both valid calls and rejected contracts without mistaking diagnostic suppression for correctness.