You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Welcome to TypeScript's second set of virtual code challenges: Type | Treat (or "Type or Treat")! We will be presenting some "spooky" code challenges that will allow you to get deeper into the TypeScript language in a fun way.
Are The Challenges For Experienced TypeScript Developers Only?
Nope! We want all developers, familiar with TypeScript or not to be apart of Type | Treat.
Beginner/Learner Challenge
Using the new music streaming service from the TypeScript team, Typify, set up and share your halloween playlist with your friends.
Head to this link and help us figure out to avoid passing typos.
Intermediate/Advanced Challenge
You've figured out your costume, but making it is a bit of a process. Can you figure out how to make all of the parts come together in a type-safe way?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The first part of the solution for this challenge used as const to trigger "Literal Inference" - basically telling TypeScript "Don't convert the array to string[] but consider it a constant set of string literals. This meant that playlist[0] stopped returning string and started returning "The Legend of Sleepy Hollow by The Monotones.mp3".
const playlist = [
"The Legend of Sleepy Hollow by The Monotones.mp3",
...
- ]+ ] as const
The second part of the challenge used typeof types to extract the type from the playlist array. Without the first change, this would be string but after the change this meant the full array of different types. You then needed to use the type index syntax[number] to declare that you want any potential string from that array.
- function playSong(song: string) {+ function playSong(song: typeof playlist[number]) {
api.play(song)
}
Successfully completing this challenge would raise an error in the final code samples due to a subtle typo.
This pattern is quite common in code we write in TypeScript codebases, you create one function which takes the result of another and keeps passing objects between functions in a pipeline. One of the best techniques for simplifying this design pattern is to use ReturnType with typeof myFunc to map the return type of one function to the paramter of another. This removes the need for intermediary types which need to be updated when the functions change.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
There is many ways to decide how to type existing data, you could use literal types when you're sure of the exact formats - or be more liberal and use string when you expect a variety. We opted for literals, but using string is totally cool too.
The second part of the challenge used type predicates (or type guards) annotates a function which returns a booleon with narrowing information about the paramters. This means we can tell TypeScript that when the return values to isRipeis true, then the argument pumpkin is of the type RipePumpkin:
- function isRipe(pumpkin: any) {+ function isRipe(pumpkin: any): pumpkin is RipePumpkin {
return "soundWhenHit" in pumpkin && pumpkin.soundWhenHit === "echo-y"
}
Successfully completing this challenge would have no errors, and the type for `.
This challenge was first about understanding different read vs write properties available in both classes and interface/typed objects. Personally, I've seen this with document.location a lot where you always get a rich object when you read but can write to that property with a string. We wanted a similar concept, but using punch which for me is generally a 'throw it all in and see what happens' style of drink.
This solution uses a mix of private class fields, indexed types and type narrowing to set up a local punch object which is always returned.
The next step was to make this class generic in some form so that a type parameter passed in to the class would dictate what the return value of a vend function was.
- class PunchMixer {+ class PunchMixer<MixerType> {+ mixer!: MixerType;
// ...
+ public vend(): MixerType {+ return this.mixer;+ }
}
We were not too worried about how you passed back the MixerType - our first draft had return {} as MixerType but a private field feels nicer.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
I wonder if we over-indexed on the difficulty here, and we're interested if you dropped off somewhere through this task because we had less submissions than usual for this challenge. The goal was to have you build out a template string literal type which accounted for string input which roughly matched how CSS's stringy variables worked.
You started with:
typeLength=string
Which accepts all possible strings, next we show some examples which should always fail. The key one here being that an empty string should fail: "". Next we provided some valid input for you to work with:
typeLength= `${number}in`
// Works with:req("0in")req("12in")
Giving you a sense that a number can be used in the template slot - which allows for all sorts of possibilities.
Next we gave samples with different prefixes, so "in" and "cm" would need to be handled. To get that right, you would need to use a union:
typeUnit="cm"|"in"typeLength= `${number}${Unit}`
// Works with:req("0in")req("12in")req("1.5cm")req("20cm")
Next we threw a curve ball - "0" should also be acceptable, this is a bit of a curve ball, but also it's a bit of a trick:
typeUnit="cm"|"in"|""typeLength= `${number}${Unit}`
// Works with:req("0in")req("12in")req("1.5cm")req("20cm")req("0")
The lack of a unit is just an empty string unit! Only one more thing now, and that is allowing a space inbetween the number and unit. This could be done via another type also:
typeUnit="cm"|"in"|""typeSpace=" "|""typeLength= `${number}${Space}${Unit}`
// Works with:req("0in")req("12in")req("1.5cm")req("20cm")req("0")req("12 cm")req("14 in")
That was is for the easy parts of the challenge. It's pretty tricky, because it requires that you understand that number can be anything in the template string and to understand how a union can allow for different types of strings inside the type. That's all in the main docs, but it could be a lot of ideas to learn at once.
This challenge also had a set of complications, cases where the version of the the Length type we expected people to build would provide interesting edge cases:
Which describes both possibile cases with a "." and without. This technique still doesn't handle the req("-12 cm"), and actually, it introduces a whole new problem: req("-12.-12cm") is allowed!
We spotted a good answer from @danvdk which revolved around using string manipulation instead, by introducing a Digit type:
This solution correctly handles the case of req("-12 cm") but via that number would allow something like req("1-22 cm") - which you can pretend is to handle an input range. It wouldn't be hard to take this solution and reasonably cover additional edge cases. Very cool solution.
You know that the type argument has to be a string, which you can tell TypeScript via <Str extends string>, then you can re-use the Str in the return position:
You'd think this would be it, but str.toUpperCase actually converts the str to a string! Tricky, you'd need to think creatively here and you have three options:
Use an as because you know better than the compiler:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This challenge aimed to be generics 101, first introducing the concept of making your function pass a type from the function to the argument:
- function getBowl(items: any) {
+ function getBowl<T>(items: T) {
return { items }
}
The any acted as hint clue about where to look, and this example is almost the first code sample on the Generics chapter in the Handbook, so it felt like a good intro.
The second part involved understanding generic constraints, these are essential tools in helping you define the baselines for types which can be used in your function. In this case we didn't provide the word "constraints" but opted for a more cryptic clue by setting up the function most of the way, then saying you only needed two words:
- function fillBowl<T>(candy: T) {
+ function fillBowl<T extends string>(candy: T) {
return { candy }
}
By saying that T extended string then the string literals are correctly passed through the function - which removes all the compiler errors.
The intermediate challenge also invovled generic constraints, so if you had just finished the beginner's then you were in a good place to figure this challenge. The key is to make a
- const check = (data: Competitor[]) => {+ const check = <Type extends Competitor> (data: Type[]) => {
return data.map(competitor => {
if (competitor.weight > 2121.5) throw new Error("Stop the show, world record hit!")
return { ...competitor, judge: (...args: unknown[]) => { } }
})
}
This is testing a few different things:
Writing generics wit han arrow function
Using an extends constraint for the interface subtypes
Re-using the type parameter inside the array
We left a tricky problem with this challenge, but explicitly didn't call it out. The function judge: (...args: unknown[]) is a types gap. There is no validating that the judge function actually works like expected. There are two approaches for handling this:
This version from @faridz974 would ensure that the right values were used in the function (e.g. you couldn't accidentally put in an object to something which could only accept string and numbers) but it ignored the order. An accurate, but whole-heartedly not recommended for production version which does take order into account comes from converting an interface to a tuple on GitHub which is a bit too long to print in here, but here's a working implementation in the playground.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Once you feel you have completed a challenge, you will need to select the Share button in the playground code editor. This will automatically copy a playground URL of the current code to your clipboard.
Then either:
Go to Twitter, and create a tweet about the challenge, add the link to your code and mention the @TypeScript Twitter account with the hashtag #TypeOrTreat.
Leave us a comment with your feedback on the dev.to post, or in this post.
If you'd like to post both of your challenges, you'll need to include two links.
Feedback
We'd love your feedback on how the Type | Treats is going, we have a 6 question survey which will help us improve.
Thanks for these great code challenges, it's super fun! 🤓
There seems to be a few typos in the following files:
(This gist seemed a good place to report them, but let me know if there's a better way!)