Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,21 @@

---

# TypeScript Foundations
# Typescript Foundations

> TypeScript Foundations is a curated set of challenges designed to build a strong foundation. From basic syntax to advanced concepts, this collection offers hands-on exercises to help you become a TypeScript Hero

## Beginer

- [Primitive Data Types](https://github.com/xam1dullo/typehero/blob/main/challenges/primitive-data-types/prompt.md) , 2023-11-22
- [Type Aliases](https://github.com/xam1dullo/typehero/blob/main/challenges/type-aliases/prompt.md), 2023-11-23
## Beginner

- [Primitive Data Types](https://github.com/xam1dullo/typehero/blob/main/challenges/primitive-data-types/prompt.md), 2023-11-22 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges/primitive-data-types/user.ts)
- [Type Aliases](https://github.com/xam1dullo/typehero/blob/main/challenges/type-aliases/prompt.md) , 2023-11-23 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges/type-aliases/user.ts)
- [Literal Types](https://github.com/xam1dullo/typehero/blob/main/challenges/literal-types/prompt.md) , 2023-11-24 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges/index-signatures/user.ts)
- [Index Signatures](https://github.com/xam1dullo/typehero/blob/main/challenges/index-signatures/prompt.md) , 2023-11-25 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges//user.ts)
- [The `typeof` Operator](https://github.com/xam1dullo/typehero/blob/main/challenges/typeof/prompt.md) , 2023-11-26 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges/typeof/user.ts)
- [Indexed Types](https://github.com/xam1dullo/typehero/blob/main/challenges/indexed-types/prompt.md) , 2023-11-27 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges/indexed-types/user.ts)
- [The `keyof` Operator](https://github.com/xam1dullo/typehero/blob/main/challenges/keyof/prompt.md) , 2023-11-29 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges/keyof/user.ts)
- [Generic Type Arguments](https://github.com/xam1dullo/typehero/blob/main/challenges/generic-type-arguments/prompt.md) , 2023-11-30 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges/generic-type-arguments/user.ts)
- [Generic Type Constraints](https://github.com/xam1dullo/typehero/blob/main/challenges/generic-type-constraints/prompt.md) , 2023-12-01 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges//user.ts)
- [Generic Type Constraints](https://github.com/xam1dullo/typehero/blob/main/challenges/mapped-object-types/prompt.md) , 2023-12-02 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges/mapped-object-types/user.ts)
- [Generic Function Arguments](https://github.com/xam1dullo/typehero/blob/main/challenges/generic-function-arguments/prompt.md) , 2023-12-03 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges/mapped-object-types/user.ts)
- [Default Generic Arguments](https://github.com/xam1dullo/typehero/blob/main/challenges/default-generic-arguments/prompt.md) , 2023-12-04 -> [Solution](https://github.com/xam1dullo/typehero/blob/main/challenges/default-generic-arguments/user.ts)
13 changes: 13 additions & 0 deletions challenges/default-generic-arguments/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"id": "default-generic-arguments",
"label": "Default Generic Arguments",
"description": "Default generic arguments allow you to optionally pass a generic argument.",
"difficulty": "beginner",
"prerequisites": [
"generic-type-arguments",
"type-unions",
"generic-type-constraints",
"indexed-types"
],
"author": "dimitropoulos"
}
64 changes: 64 additions & 0 deletions challenges/default-generic-arguments/prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
## Why Generic Arguments Need Defaults

Technically speaking, no program absolutely requires default arguments. It's a convenience feature for the humans writing programs.

Sometimes you have a value in mind for an argument and you don't want to force users of your library to constantly provide the same value over and over again. That's the situation where default arguments come in handy.

## How To Create Default Type Arguments

### First: A Refresher On Function Defaults

Let's take the case of a run-of-the-mill logging function. The logger always needs a message to log, but [log level](https://en.wikipedia.org/wiki/Syslog#Severity_level) might be something you want to be optional:

```ts
type LogLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical';

const log = (message: string, level: LogLevel = 'info') => {
// application logic
};

log('this has an explicit debug log level', 'debug');
log('this has an implicit info debug level');
```

You can do exactly this kind of thing for generic types! This is yet another parallel between functions and generic types!

### Default Generic Arguments

```ts
type Log<Message, Level = 'info'> = {
message: Message;
level: Level;
};

type ExplicitDebugLog = Log<'explicit debug', 'debug'>;
type ImplicitInfo = Log<'implicit info'>;
```

Doesn't that look super similar!? A lot of the same rules apply in types that apply to function argument defaults. For example, you can't have a required argument following a default argument.

One notable exception is that there's no TypeScript value you can pass that will work like `undefined` does for JavaScript default arguments:

```ts
const greet = (name = 'Stranger') => {
console.log(`Hello ${name}!`);
};

greet(); // Hello Stranger!
greet(undefined); // Hello Stranger!
greet('Mr. Monkey'); // Hello Mr. Monkey!
```

In TypeScript, even if you provide `never` or `unknown` or `any`, the value will be inserted instead of the default.

## Solving This Challenge

Your goal is to create some types that have have the correct default argument.

The second type, `TSConfig`, should feel a bit difficult, but if you get stuck, feel free to check out the little hint below:

### small hint

1. Start with a literal object as the parameter default.
1. Use [indexed types](https://typehero.dev/challenge/indexed-types).
1. The error you'll see is because of a missing [generic constraint](https://typehero.dev/challenge/generic-type-constraints).
8 changes: 8 additions & 0 deletions challenges/default-generic-arguments/solutions/1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type ApiRequest<Data, Method = 'GET'> = {
data: Data;
method: Method;
};

type TSConfig<Config extends { strict: boolean } = { strict: true }> = {
strict: Config['strict'];
};
17 changes: 17 additions & 0 deletions challenges/default-generic-arguments/tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Expect, Equal } from 'type-testing';

type test_ApiRequest_explicitPost = Expect<
Equal<ApiRequest<string, 'POST'>, { data: string; method: 'POST' }>
>;

type test_ApiRequest_implicitGet = Expect<
Equal<ApiRequest<number>, { data: number; method: 'GET' }>
>;

type test_TSConfig_default = Expect<Equal<TSConfig, { strict: true }>>;

type test_TSConfig_true = Expect<Equal<TSConfig<{ strict: true }>, { strict: true }>>;

type test_TSConfig_false = Expect<Equal<TSConfig<{ strict: false }>, { strict: false }>>;

type test_TSConfig_boolean = Expect<Equal<TSConfig<{ strict: boolean }>, { strict: boolean }>>;
7 changes: 7 additions & 0 deletions challenges/default-generic-arguments/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}
8 changes: 8 additions & 0 deletions challenges/default-generic-arguments/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type ApiRequest<T, TMethod extends string = "GET"> = {
data: T;
method: TMethod;
};

type TSConfig<T = { strict: true }> = {
[K in keyof T]: T[K];
};
8 changes: 8 additions & 0 deletions challenges/generic-function-arguments/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "generic-function-arguments",
"label": "Generic Function Arguments",
"description": "Give generic function types to your functions",
"difficulty": "beginner",
"prerequisites": ["generic-type-arguments", "literal-types"],
"author": "dimitropoulos"
}
76 changes: 76 additions & 0 deletions challenges/generic-function-arguments/prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
## Why Functions Need Generics

One of the most common needs a TypeScript developer faces is the need to pass generic type arguments to functions. Then, those functions can use and manipulate those parameters.

In many situations, we don't need functions to be generic. The function might be written in such a way as to only work for one type of data (e.g. `string` or `number`). But, other times, we write functions where we want to pass the data type along from input to output.

## Understanding Generic Syntax

In a previous challenge on [generic type arguments](https://typehero.dev/challenge/generic-type-arguments) we saw a `Row` type that looked like this:

```ts
interface Row<T> {
label: string;
value: T;
disabled: boolean;
}
```

Imagine if you had a function `createRow` that returns an object that looked like our `Row` type:

```ts
const createRow = (
label: string,

// how do we provide a type for `value`?
value: unknown,

disabled = false,
) => ({
label,
value,
disabled,
});
```

Similarly to how we used the `<` and `>` for our `Row` interface, we place the `<` and `>` right before the parenthesis that start our function's arguments.

```ts
const createRow = <T,>(label: string, value: T, disabled = false) => ({
//...
});
```

This is great! Now our `value` parameter has the right type!

### A Word On A Syntax Quirk

You may see the `<T,>` syntax out in the wild. There's an unfortunate inconvenience with TypeScript's syntax that this is used to work around. Imagine being a parser and seeing `const createRow = <T>`. If you don't know what's coming next it could be ambiguous between these two things

```ts
// Thing 1: Start of JSX
const createRow = <T>Some JSX stuff!</T>;

// Thing 2: start of generic function
const createRow = <T>(someArg: T) => {
/* some function stuff */
}
```

Because of this ambiguity, we need to do something to _disambiguate_ these two situations to the TypeScript compiler. In the early days it was common to use `<T extends unknown>` (or `<T extends any>` before `unknown` existed in TypeScript); however, over time people realized there was an easier way: `<T,>`. Since `,` is invalid in JSX, but valid in TypeScript when separating multiple arguments, it's appropriate to use this to disambiguate the two situations to the TypeScript compiler.

With that being said, if you see this strange syntax, please note that it is not you, it's just TypeScript being a bit funky. If this is confusing to you, just skip it and come back here and read it again when the day comes that you first see this syntax out in the wild.

## Solving This Challenge

This challenge asks you to make an `identity` function. This function does nothing and returns what it was passed with no modifications.

> Note: mathematicians love `identity` operations, so computer scientists use them too so we can seem cool to the mathematicians.

The next function (`mapArray`) is admittedly pretty hard, but take it slow and think about it one step at a time. Ask yourself:

> what is the type of `arr`

Then, provide a generic argument as a type for `arr`. After you do that, think about what types you need to provide for `fn`.

In this particular case, if you spend a few minutes with it don't be afraid to check out some solutions. The second example is here to widen your perspective more than anything else. It's good to wrestle with tough problems, even ones that are above your ability level.
3 changes: 3 additions & 0 deletions challenges/generic-function-arguments/solutions/1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const identity = <T>(input: T) => input;

const mapArray = <T, U>(arr: T[], fn: (item: T) => U) => arr.map(fn);
58 changes: 58 additions & 0 deletions challenges/generic-function-arguments/tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Expect, Equal } from 'type-testing';

/** temporary */
const expect = <T>(a: T) => ({
toEqual: (b: T) => a === b
});

const identity_string = identity("this is a string");
expect(identity_string).toEqual("this is a string");
type test_identity_string = Expect<Equal<
typeof identity_string,
"this is a string"
>>;

const identity_number = identity(123.45);
expect(identity_number).toEqual(123.45);
type test_identity_number = Expect<Equal<
typeof identity_number,
123.45
>>;

const identity_boolean = identity(false);
expect(identity_boolean).toEqual(false);
type test_identity_boolean = Expect<Equal<
typeof identity_boolean,
false
>>;

const strings = ['1', '1', '2', '3', '5'];
const numbers = [1, 1, 2, 3, 5];

const stringsToNumbers = mapArray(strings, str => parseInt(str));
expect(stringsToNumbers).toEqual(numbers);
type test_stringsToNumber = Expect<Equal<
typeof stringsToNumbers,
number[]
>>;

const numbersToStrings = mapArray(numbers, num => `${num}`);
expect(numbersToStrings).toEqual(strings)
type test_numbersToStrings = Expect<Equal<
typeof numbersToStrings,
string[]
>>;

const numbersToNumbers = mapArray(numbers, num => num + 1);
expect(numbersToNumbers).toEqual([2, 2, 3, 4, 6])
type test_numbersToNumbers = Expect<Equal<
typeof numbersToNumbers,
number[]
>>;

const stringsToStrings = mapArray(strings, str => `${str}!`);
expect(stringsToStrings).toEqual(['1!', '1!', '2!', '3!', '5!'])
type test_stringsToStrings = Expect<Equal<
typeof stringsToStrings,
string[]
>>;
7 changes: 7 additions & 0 deletions challenges/generic-function-arguments/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}
3 changes: 3 additions & 0 deletions challenges/generic-function-arguments/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const identity = <T>(val: T) => val;

const mapArray = <T, U>(arr: T[], fn: (val: T) => U) => arr.map(fn);
8 changes: 8 additions & 0 deletions challenges/indexed-types/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "indexed-types",
"label": "Indexed Types",
"description": "Some types can be indexed to lookup a particular value.",
"difficulty": "beginner",
"prerequisites": ["primitive-data-types", "type-unions", "keyof", "literal-types"],
"author": "dimitropoulos"
}
Loading