Checkpoint: Decoder Types

The Course
•
4 min read

Close the earlier solutions before this checkpoint. Rebuild a familiar unknown-to-Result decoder, then derive its output type. This combines intermediate validation with conditional inference rather than asking you to invent another runtime abstraction.

Decoder<T> is a function contract. Decoded<D> matches that contract to recover its success value type. The decoder still has to inspect its input. A conditional type can describe what the decoder promises, but cannot prove its body validates correctly.

Use a count that allows zero and rejects negative, fractional and nonnumeric inputs. Keep a distinct error branch. Your success type should follow from typeof decodeCount rather than repeating number at the consumer. Work for about twenty minutes before opening hints.

Predict, check, and run

Write the output before running. Use Check types first, then Check & run. Types disappear at runtime: explain both what the compiler checks and what the code actually does. Each editor is an independent ES module; await asynchronous work explicitly.

Predict, then runTypeScript
type Result<T> =
  | {
      ok: true;

      value: T;
    }
  | {
      ok: false;

      message: string;
    };

type Decoder<T> = (value: unknown) => Result<T>;

type Decoded<D> = D extends Decoder<infer T> ? T : never;

const decodeText: Decoder<string> = value =>
  typeof value === "string"
    ? { ok: true, value }
    : { ok: false, message: "Not text" };
const title: Decoded<typeof decodeText> = "Types";

console.log(title);

Enable JavaScript to edit and type-check this example.

Check your prediction
Types

Acceptance cases before you code

Write a small test plan before implementing derived decoder success type. These cases extend the brief; they are not a second worked solution. Predict the expected behavior independently, then test through the public function. A good ordinary example and a good boundary example should catch different possible mistakes.

Case to tryWhat your result should demonstrate
Valid zeroReturn success and preserve its derived numeric type
Text zero or fractional numberReject at runtime
Consumer assigns wrong value typeCompiler rejects the derived contract

After the core task passes, deliberately break one rule and identify the case that notices it. Restore the implementation and explain the failure in terms of the contract, not the number of assertions. If a case requires a concept you have not recalled, revisit the relevant lesson before opening the solution.

Build it yourself

Exercise

Checkpoint: Decoder Types: practise

Implement decodeCount and Decoded. Derive Count using typeof decodeCount, check zero succeeds and “0” fails, then print 0 and false. Assert that negative and fractional inputs also fail.

Your workspaceTypeScript
// Start from the stated contract. Add a valid call and an edge case.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

The runtime numeric checks and the type extractor are separate responsibilities.

Hint 2: a first step

First declare the decoder as Decoder<number>; then match Decoder<infer T> in the alias.

Show one solution
One possible solutionTypeScript
type Result<T> =
  | {
      ok: true;

      value: T;
    }
  | {
      ok: false;

      message: string;
    };

type Decoder<T> = (value: unknown) => Result<T>;

type Decoded<D> = D extends Decoder<infer T> ? T : never;

const decodeCount: Decoder<number> = value => {
  if (
    typeof value !== "number" ||
    !Number.isFinite(value) ||
    !Number.isInteger(value) ||
    value < 0
  ) {
    return { ok: false, message: "Invalid count" };
  }

  return { ok: true, value };
};

type Count = Decoded<typeof decodeCount>;

const zero: Count = 0;
const valid = decodeCount(zero);

for (const input of [-1, 1.5, Infinity]) {
  if (decodeCount(input).ok) {
    throw new Error("Invalid count accepted");
  }
}

console.log(valid.ok ? valid.value : valid.message);
console.log(decodeCount("0").ok);

Enable JavaScript to edit and type-check this example.

0
false

Trace the implementation against the task before comparing styles. A passing example supports that case; use the changes below to challenge the wider contract.

Change it and test it

Add null and NaN cases. Then deliberately weaken the runtime predicate while leaving Decoder<number> intact. Explain why type extraction still passes and a behavioral test must catch the defect.

Was this article helpful?

Your response is saved on this device.