Daniel sent us a challenge episode this time — TypeScript edition. He wants us to coach a complete beginner, someone who's never written a line of code, through building a small but real TypeScript program using only our voices. No screen, no slides, just two hosts and a listener at a keyboard. We need to pick one genuinely useful program, verify it works, explain the why behind everything, and preempt the mistakes beginners actually make. He suggested a typed to-do list CLI, a tip calculator with strict types, or a small function library with interfaces and union types. I say we go with the tip calculator. It's immediately useful, it's got numbers and strings and functions, and it forces us to explain types in a way that actually clicks.
Oh, perfect choice. A tip calculator gives us numbers, string inputs, a function with a return type, union types if we get fancy with service ratings, and the whole compile-and-run flow. Plus the listener ends up with something they can actually use next time they're splitting a dinner bill. By the way, today's episode script is being generated by DeepSeek V four Pro, which feels appropriate for a TypeScript challenge.
Alright, let's get into it. If you're listening and you've never coded before, welcome. You're about to build a real program. All you need is a computer, an internet connection, and the willingness to type some very strange punctuation out loud with us.
Before we write a single line of TypeScript, we need to get the tools installed. TypeScript is a superset of JavaScript — it adds type annotations, but browsers and Node.js don't understand TypeScript directly. You have to compile it down to plain JavaScript first. That means we need Node.js, which lets you run JavaScript outside a browser, and the TypeScript compiler itself.
Open your web browser and go to nodejs dot org. Download the L T S version — Long Term Support. It's the stable one. Run the installer. It'll put Node and something called npm on your system. npm is the Node package manager — it's how you install tools like TypeScript. Once it's installed, open your terminal. On Windows that's PowerShell or Command Prompt. On Mac it's Terminal. On Linux you already know where it is.
To check it worked, type node space dash dash version and hit enter. You should see a version number like v twenty two point something. Then type npm space dash dash version. If you see both, you're in business.
Now type npm space install space dash g space typescript. The dash g means global — it installs TypeScript so you can use it from any folder. When it finishes, type tsc space dash dash version. You should see a TypeScript version number. tsc stands for TypeScript Compiler.
Quick note here. Some tutorials will tell you to use tsx or ts-node to run TypeScript directly without a separate compile step. Those are great tools, but for a beginner I want you to understand the two-step flow first. Write TypeScript, compile to JavaScript, run the JavaScript. Once that mental model is solid, then you can reach for shortcuts.
Alright, now create a folder somewhere on your computer. Call it tip-calculator. Open that folder in your terminal. If you don't know how to navigate in a terminal, type cd space and then the path to your folder. cd stands for change directory. Once you're inside your project folder, type tsc space dash dash init and hit enter.
This creates a file called tsconfig dot json. That's the configuration file for TypeScript. Open it in any text editor — Notepad, V S Code, whatever you've got. It's a JSON file full of options, most of them commented out. For a beginner, the most important thing to know is that this file tells TypeScript how strictly to check your code. There's an option called strict that's set to true by default. Leave it on. Strict mode enables a bunch of checks that catch errors before they happen. It'll feel annoying at first, like a pedantic friend who corrects your grammar, but it's saving you from bugs you won't even know exist yet.
That's really the thesis of TypeScript. JavaScript will let you do almost anything — add a number to a string, pass the wrong kind of data to a function, access properties that don't exist — and it'll just silently produce weird results or crash at runtime. TypeScript catches those mistakes while you're still writing, before the code ever runs. The compiler is like a spellchecker for logic.
Alright, let's write our first line of code. In your project folder, create a new file called index dot ts. The dot ts extension is how TypeScript knows this file contains TypeScript code. Open it in your editor. Type this exactly.
Const space greeting space colon space string space equals space quote Hello comma space world exclamation mark quote semicolon. Let me say that in plain language. You're declaring a constant variable called greeting. The colon string part is a type annotation — it says this variable can only ever hold a string, which is programming-speak for text. Then you assign it the value quote Hello world exclamation mark quote. Then a semicolon at the end.
Now type console dot log open parenthesis greeting close parenthesis semicolon. This tells the program to print the value of greeting to the terminal. Save the file. Now go back to your terminal, make sure you're in the project folder, and type tsc space index dot ts.
If you typed everything correctly, nothing dramatic happens. tsc runs, checks your code for type errors, and produces a new file called index dot js. That's your compiled JavaScript. If you made a typo, you'll see an error message with a line number telling you where the problem is. Read it carefully — TypeScript error messages are actually pretty good at telling you exactly what went wrong.
Now type node space index dot js and hit enter. You should see Hello world exclamation mark printed in your terminal. Congratulations, you just wrote, compiled, and ran your first TypeScript program. That's the whole flow. Write dot ts, compile with tsc, run the resulting dot js with node.
Now here's your first common mistake. Beginners often try to run the dot ts file directly with node — node space index dot ts. Node doesn't understand TypeScript. It'll throw a syntax error because it sees colons and type annotations and has no idea what to do with them. The compilation step strips all that TypeScript-specific syntax and leaves you with plain JavaScript that node can run. The type annotations are for you and the compiler. They don't exist at runtime.
Alright, let's build something real. Delete everything in index dot ts and let's write a tip calculator. Here's the plan. The program will ask for the bill amount, the tip percentage, and the number of people splitting the bill. It'll calculate the tip, the total, and the amount each person owes. We're going to build it step by step, and I want you to compile and run after each step.
We need to get input from the user. Node has a built-in module for reading from the terminal. At the very top of your file, type this. import space open curly brace space createInterface space close curly brace space from space quote readline close quote semicolon. This imports a specific function called createInterface from Node's readline module.
Now let's set up the readline interface. const space rl space colon space ReturnType open angle bracket typeof space createInterface close angle bracket space equals space createInterface open parenthesis open curly brace. New line, indent two spaces. input colon space process dot stdin comma. output colon space process dot stdout comma. New line, close curly brace close parenthesis semicolon.
Okay, that's a lot of punctuation. Let me unpack what's happening. We're creating a readline interface called rl that reads from standard input — what the user types — and writes to standard output — what appears in the terminal. The type annotation uses ReturnType, a built-in utility type that extracts the return type of a function. TypeScript can infer this, but I'm being explicit for pedagogical reasons. In practice, you'd just write const rl equals createInterface and let TypeScript figure out the type. That's called type inference, and it's one of TypeScript's best features.
That's an important point. You don't need to annotate everything. TypeScript is smart enough to figure out that if you assign a string literal to a variable, that variable is a string. The colon string annotations are most useful on function parameters and return types, where the compiler can't always guess what you intend.
Now let's write the function that asks a question and returns the answer. const space askQuestion space equals space open parenthesis query space colon space string close parenthesis space colon space Promise open angle bracket string close angle bracket space equals greater than space open curly brace. return space new space Promise open parenthesis open parenthesis resolve close parenthesis space equals greater than space open curly brace. rl dot question open parenthesis query comma space resolve close parenthesis semicolon. close curly brace close parenthesis semicolon. close curly brace semicolon.
Let me say that in human. We're declaring a function called askQuestion that takes one parameter called query, which must be a string. The function returns a Promise that resolves to a string. A Promise is JavaScript's way of handling something that takes time — like waiting for a user to type something. Inside, we create a new Promise that calls rl dot question, which displays the query and waits for the user to type something and press enter. When they do, the resolve function is called with whatever they typed.
The type annotations here are doing real work. The colon string on the query parameter means TypeScript will yell at you if you try to pass a number to askQuestion. The colon Promise open angle bracket string close angle bracket after the parameter list is the return type annotation. If you forget to return something, or return the wrong type, TypeScript catches it at compile time rather than you discovering it at runtime when your program crashes.
Now let's write the main function that orchestrates the whole calculator. This is going to be async because we're using await with our Promises. const space main space equals space async space open parenthesis close parenthesis space colon space Promise open angle bracket void close angle bracket space equals greater than space open curly brace. New line, indent two spaces. console dot log open parenthesis quote Tip space Calculator quote close parenthesis semicolon. console dot log open parenthesis quote dash dash dash dash dash dash dash dash dash dash dash dash dash dash dash dash quote close parenthesis semicolon.
Const space billString space equals space await space askQuestion open parenthesis quote Enter space the space bill space amount colon space dollar sign quote close parenthesis semicolon. const space tipPercentString space equals space await space askQuestion open parenthesis quote Enter space the space tip space percentage colon space quote close parenthesis semicolon. const space peopleString space equals space await space askQuestion open parenthesis quote Enter space number space of space people colon space quote close parenthesis semicolon.
Now here's where types get interesting. askQuestion always returns a string, because that's what the user types. But we need to do math on these values, and you can't multiply strings. We need to convert them to numbers. const space bill space colon space number space equals space parseFloat open parenthesis billString close parenthesis semicolon. const space tipPercent space colon space number space equals space parseFloat open parenthesis tipPercentString close parenthesis semicolon. const space people space colon space number space equals space parseInt open parenthesis peopleString comma space ten close parenthesis semicolon.
ParseFloat converts a string to a floating-point number — a number that can have decimal places. parseInt converts a string to an integer — a whole number. The comma ten is the radix, which tells parseInt we're using base ten. Always include the radix. Without it, parseInt can do weird things with strings that start with zero.
Now the actual calculation. const space tipAmount space colon space number space equals space bill space asterisk space open parenthesis tipPercent space slash space one hundred close parenthesis semicolon. const space total space colon space number space equals space bill space plus space tipAmount semicolon. const space perPerson space colon space number space equals space total space slash space people semicolon.
Asterisk is multiplication, slash is division. The tip percentage divided by one hundred converts, say, fifteen percent to zero point one five. Then we multiply by the bill to get the tip amount. Add that to the bill for the total. Divide by people for the per-person split.
Now we need to display the results. We want to format the numbers to two decimal places, because that's how money works. Let me spell out the first line carefully. console dot log open parenthesis backtick. Then a newline character — backslash n. Then Tip space amount colon space dollar sign. Then a dollar sign followed by open curly brace. Then tipAmount dot toFixed open parenthesis two close parenthesis. Then close curly brace. Then close backtick. Then close parenthesis semicolon.
That's a template literal. The backticks let you embed expressions inside a string using dollar sign curly brace. The backslash n is a newline character. toFixed with two rounds to two decimal places. So if the tip is five point seven five dollars, it'll display as five point seven five, not five point seven five zero zero zero whatever.
console dot log open parenthesis backtick Total space amount colon space dollar sign dollar sign open curly brace total dot toFixed open parenthesis two close parenthesis close curly brace backtick close parenthesis semicolon. console dot log open parenthesis backtick Each space person space pays colon space dollar sign dollar sign open curly brace perPerson dot toFixed open parenthesis two close parenthesis close curly brace backtick close parenthesis semicolon.
The double dollar sign — the first one is the literal dollar sign character in the output, the second one is the template literal syntax for embedding an expression. It looks weird in text but it works.
Now we need to close the readline interface and end the program. rl dot close open parenthesis close parenthesis semicolon. close curly brace semicolon. That's the closing brace of the main function. Then one more line. main open parenthesis close parenthesis semicolon. That actually calls the main function and starts everything running.
Alright, save the file. Go to your terminal. Type tsc space index dot ts and hit enter. If there are no errors, type node space index dot js and hit enter. You should see the tip calculator title, then it'll prompt you for the bill amount. Type a number like forty five point fifty and hit enter. Then the tip percentage, like eighteen and hit enter. Then the number of people, like three and hit enter. It should print the tip amount, total, and per-person split, all formatted to two decimal places.
If you get errors during compilation, here are the most likely culprits. One, you typed colon instead of semicolon somewhere. Remember, colons go in type annotations — const x colon number equals five — and semicolons go at the end of statements. Two, you forgot a closing parenthesis, curly brace, or backtick. TypeScript's error messages will point you to the line. Three, you typed angle bracket or some other punctuation wrong. Pause the podcast, read the error message, look at the line number it mentions, and compare what you typed to what we described.
Missing semicolons, by the way, are generally not fatal. JavaScript and TypeScript have automatic semicolon insertion, so you can often get away without them. But missing colons in type annotations are absolutely fatal. If you write const x number equals five without the colon after x, TypeScript thinks you're trying to do something weird with destructuring and the error message will be baffling. The colon is load-bearing.
Now let's talk about what static typing actually bought us in this program. Every variable has a known type. bill is a number, tipPercent is a number, people is a number. If we accidentally tried to do bill dot toUpperCase somewhere, TypeScript would catch it immediately because numbers don't have a toUpperCase method. In plain JavaScript, that would just crash at runtime with a cryptic error. TypeScript moves that failure to compile time, when you can fix it before anyone ever runs the code.
The function signatures are also checked. If we called askQuestion with a number instead of a string, TypeScript would flag it. If we forgot to await one of the askQuestion calls, TypeScript would notice that we're trying to pass a Promise where a string is expected. These are exactly the kinds of bugs that waste hours of debugging time in JavaScript projects.
Let me introduce a concept that's central to TypeScript and often confuses beginners — the difference between type and interface. Both let you describe the structure of an object. For example, you could write interface Bill open curly brace amount colon number semicolon tipPercent colon number semicolon people colon number semicolon close curly brace. Or you could write type Bill equals open curly brace amount colon number semicolon tipPercent colon number semicolon people colon number semicolon close curly brace.
They look almost identical and in many cases they're interchangeable. The practical difference is that interfaces are extendable — you can declare the same interface twice and TypeScript merges them. Types are not — a type alias is fixed once declared. For a beginner, the rule of thumb is use interface for object shapes, especially if you're building a public A P I that others might extend. Use type for unions, intersections, and more complex type expressions. But honestly, for small programs, either works and the distinction isn't worth obsessing over.
Speaking of unions, that's another TypeScript feature that would improve our tip calculator. Right now, tipPercent is just a number. The user could type negative fifty or two thousand and our program would happily calculate nonsense. With a union type, we could constrain it. Something like type ServiceRating equals quote terrible close quote pipe quote poor close quote pipe quote average close quote pipe quote good close quote pipe quote excellent close quote semicolon. The pipe character means or. Then we could map each rating to a percentage. That's a union type — a value that can be one of several specific string literals.
That's where TypeScript really shines over JavaScript. In JavaScript, you'd have to write runtime checks — if tipPercent is less than zero or greater than one hundred, show an error. In TypeScript, you can encode those constraints in the type system itself, so invalid states become impossible to represent. The phrase you hear a lot is "make illegal states unrepresentable." That's the TypeScript philosophy.
Let's talk about the any escape hatch. TypeScript has a special type called any that opts out of type checking entirely. Beginners are often tempted to use any when they can't figure out the correct type. Every any in your code is a hole in the safety net. The whole point of using TypeScript is to catch errors at compile time, and any undoes that. If you don't know the type, use unknown instead. unknown is like any but it forces you to check the type before you use the value.
That's a good distinction. any says trust me, I know what I'm doing. unknown says I don't know what this is, make me check before I use it. TypeScript's strict mode, which we enabled in tsconfig, actually flags implicit any — places where TypeScript can't infer the type and would default to any. That forces you to either annotate explicitly or restructure your code so the inference works.
Alright, let's add input validation to our tip calculator so it's actually robust. We want to handle the case where the user types something that's not a number. Right now, parseFloat of a non-numeric string returns NaN, which stands for Not a Number, and all our math produces NaN, and the output is NaN dollars which is not helpful.
Let's write a helper function. After the askQuestion function but before main, add this. const space parseNumber space equals space open parenthesis value space colon space string close parenthesis space colon space number space pipe space null space equals greater than space open curly brace. const space parsed space equals space parseFloat open parenthesis value close parenthesis semicolon. return space isNaN open parenthesis parsed close parenthesis space question mark space null space colon space parsed semicolon. close curly brace semicolon.
The return type is number pipe null. The pipe means union — this function returns either a number or null. Inside, we use isNaN to check if the parsed result is Not a Number. The question mark colon is the ternary operator — it's an if-else in a single line. If isNaN returns true, we return null. Otherwise we return the parsed number.
Now update the main function. Replace the parseFloat and parseInt lines with this. const space bill space equals space parseNumber open parenthesis billString close parenthesis semicolon. if space open parenthesis bill space equals equals equals space null close parenthesis space open curly brace. console dot log open parenthesis quote Invalid space bill space amount quote close parenthesis semicolon. rl dot close open parenthesis close parenthesis semicolon. close curly brace.
Do the same for tipPercent and people, using parseNumber for both. The equals equals equals is the strict equality operator. Three equals signs means check both value and type. Two equals signs does type coercion and can produce surprising results. Always use triple equals.
Now compile and run again. Try entering "abc" for the bill amount. The program should print "Invalid bill amount" and exit cleanly instead of producing NaN dollars. That's a real program now — it handles bad input gracefully.
Let me explain what's happening with the types here. parseNumber returns number pipe null. When we assign the result to bill, TypeScript infers that bill is also number pipe null. But then we have the if check — if bill equals equals equals null, we return early. TypeScript is smart enough to understand this. After the if block, it knows bill cannot be null, because we already handled that case. So inside the rest of the function, bill is treated as a plain number. This is called type narrowing, and it's one of TypeScript's most powerful features.
That's the compiler doing real reasoning about your program's control flow. It understands that after a null check, the value can't be null. After a typeof check, the type is narrowed to whatever you checked. After checking if a property exists, that property is known to exist in the subsequent code. You get all of this for free just by writing normal JavaScript-style checks, and TypeScript follows along.
Now let's talk about the compile step in more depth. When you run tsc, it parses your dot ts files, strips all the type annotations, and outputs plain JavaScript. The type annotations are erased — they don't exist in the output. This means TypeScript has zero runtime cost. The JavaScript that runs is exactly what you would have written by hand, minus the types.
The tsconfig dot json file controls how strict this process is. The strict flag enables a family of options. strictNullChecks makes sure you handle null and undefined explicitly. noImplicitAny prevents TypeScript from silently falling back to the any type. strictFunctionTypes checks function parameter types more rigorously. Together they make TypeScript much more useful as a bug-finding tool. For a beginner, my advice is to start with strict true and not touch anything else until you encounter a specific problem you need to solve. The defaults are sensible.
Let's talk about one more common beginner confusion — the difference between compile-time and runtime. TypeScript checks your types at compile time, when you run tsc. But at runtime, when node runs your JavaScript, there are no type checks. If you fetch data from an A P I, TypeScript can't guarantee that the data matches the type you declared. If you said the A P I returns an object with an amount property that's a number, but the A P I actually returns a string, TypeScript won't catch that. You need runtime validation for data that crosses the boundary of your program.
That's a crucial point. TypeScript protects you from mistakes in your own code. It doesn't protect you from the outside world. Libraries like Zod exist specifically for runtime validation, and they integrate with TypeScript to give you both compile-time and runtime safety. But that's a topic for another day.
Alright, let's do a quick recap of what our listener has built. A working tip calculator that prompts for bill amount, tip percentage, and number of people, validates that the inputs are actual numbers, calculates the tip, total, and per-person split, and displays them formatted as currency. It's typed end to end. It handles errors gracefully. It compiles cleanly with strict mode enabled.
They've learned the core TypeScript concepts along the way. Type annotations with colon. Type inference — letting TypeScript figure out types automatically. Union types with the pipe operator. Type narrowing through control flow checks. The difference between any and unknown. Async functions returning Promises. And the compile-then-run workflow.
If you got stuck anywhere, here's what I want you to do. Don't start over. Look at the error message. Read it out loud. TypeScript error messages can look intimidating, but they almost always tell you exactly what's wrong and on which line. The most common one you'll see is something like "Type X is not assignable to type Y." That means you declared something as one type but used it as another. Trace back to where you declared the type and where you used it, and one of them is wrong.
Another common one is "Cannot find name" which usually means you have a typo in a variable name or you forgot to declare something. And "Property does not exist on type" means you're trying to access something that TypeScript doesn't know about. Maybe you spelled the property name wrong, or maybe you need to narrow the type first. Here's a tip that'll save you hours. When you're confused about what type TypeScript has inferred for something, hover over the variable in your editor. Most editors with TypeScript support will show you the inferred type in a tooltip. You don't need to guess. The compiler knows, and it'll tell you.
Alright, let's do one more enhancement to the program to demonstrate interfaces. Let's define the shape of our calculation result. After the imports, add this. interface space CalculationResult space open curly brace. bill space colon space number semicolon. tipPercent space colon space number semicolon. tipAmount space colon space number semicolon. total space colon space number semicolon. people space colon space number semicolon. perPerson space colon space number semicolon. close curly brace.
This interface says any object that claims to be a CalculationResult must have all six of these properties, each with the specified type. Now we can write a function that takes a CalculationResult and formats it for display. const space formatResult space equals space open parenthesis result space colon space CalculationResult close parenthesis space colon space string space equals greater than space open curly brace. return space backtick. Then format it with the same toFixed calls we used before, but accessing result dot bill, result dot tipAmount, and so on. Close the backtick and add a semicolon. Close the curly brace.
Now in main, after calculating everything, you can create a result object. const space result space colon space CalculationResult space equals space open curly brace bill comma space tipPercent comma space tipAmount comma space total comma space people comma space perPerson close curly brace semicolon. Then just console dot log open parenthesis formatResult open parenthesis result close parenthesis close parenthesis semicolon.
This is cleaner. All the formatting logic lives in one function. The main function just orchestrates. If you later want to change how the output looks, you only change formatResult. The interface ensures that you can't accidentally pass an incomplete object to formatResult. If you forget a property, TypeScript tells you. Compile and run one more time. Everything should still work, but now the code is better organized. This is the kind of refactoring that TypeScript makes safe. In JavaScript, if you renamed a property or changed its type, you'd have to manually check every place that uses it. In TypeScript, the compiler checks for you. You can restructure with confidence.
Let's address one thing we glossed over. The readline module. When you import from quote readline close quote without a dot slash prefix, Node looks for a built-in module or a package in node_modules. readline is built into Node, so it just works. Also, we used E S module syntax — import and export — which is the modern standard. For this to work in Node without extra configuration, your package dot json needs a type field set to module, or your files need the dot mts and dot mjs extensions. If you get an error about import syntax, that's why. The quick fix is to name your file index dot mts instead of index dot ts, and compile to index dot mjs. The m stands for module.
Or you can run npm space init space dash y in your project folder to create a package dot json, then add quote type close quote colon space quote module close quote to it. That tells Node to treat dot js files as E S modules. The dash y flag accepts all defaults so you don't have to answer the interactive prompts.
Alright, I think we've built a real program and explained the why behind everything. Let me give you a few extensions to try on your own if you want to keep going. One, add a service rating input using the union type we described. Map terrible to ten percent, poor to twelve, average to fifteen, good to eighteen, excellent to twenty-two. Two, add a split-by-item feature where you enter multiple item prices and the program sums them before calculating the tip. Three, write the results to a file using Node's fs module instead of just printing them.
If you want to learn more, the official TypeScript handbook at typescriptlang dot org is excellent. It's well-written, it has examples you can run, and it covers everything from basic types to advanced generics. It's free and it's the best resource out there.
Now: Hilbert's daily fun fact.
The collective noun for a group of porcupines is a prickle.
What can a listener actually do with what we covered today? First, you've got a working TypeScript environment on your machine. You can write dot ts files, compile them, and run them. You understand the compile step and why it exists. Second, you've internalized the core type concepts — annotations, inference, unions, narrowing, interfaces. These show up in every TypeScript codebase. Third, you've seen how static typing catches bugs before they happen, not after. The next time you write JavaScript and something mysteriously breaks, you'll understand what TypeScript would have caught.
The bigger takeaway is that audio-only coding instruction actually works if you're disciplined about spelling everything out. We said every angle bracket, every curly brace, every semicolon. We paused for the listener to type and run. We explained the why, not just the what. If you followed along, you didn't just copy code — you understood what it does and why it's typed that way. That's the proof of concept Daniel was asking for.
TypeScript specifically rewards this approach. The compiler gives you immediate feedback. If you made a typo, it tells you. If you got the types wrong, it tells you. It's an interactive learning tool built into the language itself. Every error message is a mini-lesson.
One forward-looking thought. The trend in the JavaScript ecosystem is toward more type safety, not less. TypeScript adoption has gone from something like twenty percent of projects a few years ago to over seventy percent in recent surveys. New tools like the type-checker in Vite and the type-stripping proposals for JavaScript itself suggest that types are becoming part of the platform, not just an add-on. Learning TypeScript today is investing in where the ecosystem is already heading.
Thanks to our producer Hilbert Flumingtop for keeping this show running. This has been My Weird Prompts, episode two thousand four hundred nineteen. You can find every episode at myweirdprompts dot com or wherever you get your podcasts. If you built the tip calculator, we'd love to hear about it.
Until next time, keep your types strict and your semicolons optional.