Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    TypeScript runtime vs types at run-time

    Last week we talked about how nice it is that Bun runs TypeScript directly, but that having types at run-time would be even better. A reader asked "wtf does that even mean". Great question!

    What did you mean by "No run-time support for types though"? I suppose more specifically, what exactly does node/browser support but not Bun in this case? ~ Michael

    Bun is a new JavaScript runtime that aims to act as a drop-in replacement for NodeJS. My Bun first impressions are that it's super fast, easy to learn, and the perfect fit for CLI scripts.

    Now, what's up with those types?

    TypeScript types work the same in Bun and every other JavaScript runtime. Nothing's missing, but Bun has an opportunity to do more, if they ever choose to.

    TypeScript adds static types to JavaScript

    TypeScript's 2 core design principles are:

    1. Add static type support to JavaScript without limiting the language
    2. No run-time overhead

    That's my distillation of TypeScript Design Goals.

    Static typing means you can have a program (the TypeScript compiler) that checks your code looks valid without running the code itself. It can make sure every function that takes a string, is always called with an argument that you pinky swear promise is going to be a string.

    Your editor highlights mistakes with a squiggly line and the compiler won't let you build the code.

    Squiggly line says you made a mistake
    Squiggly line says you made a mistake

    This makes entire classes of bugs and mistakes impossible. And there's lots of research showing that static typing reduces bugs. Here's a paper that quantifies detectable bugs in JavaScript. And another more recent paper that compares JavaScript and TypeScript projects on GitHub.

    TypeScript projects have fewer code smells and lower cognitive complexity 💪

    Fewer code smells in TS
    Fewer code smells in TS
    Lower cognitive complexity in TS
    Lower cognitive complexity in TS

    Types at run-time

    TypeScript relies on what you pinky swear promise. If you say this is a string, okay it's a string.

    TypeScript trusts you
    TypeScript trusts you

    The compiler is smart enough to say "Hey you can't turn a number into a string! Make it unknown first". I hope this wouldn't pass code review 😅

    But TypeScript can't check the true type of a value when you run the code. It compiles away! That's why there's no overhead – no type checking at run-time.

    In a program that takes no inputs this is fine. You can track the provenance of a value from start to finish, know all its transformations because functions are typed, etc. You always know the type and you can trust the type.

    Input is messy, always

    Inputs mess this up.

    type User {
    	id: number
    	name: string
    }
    
    const user: User = await fetch('api/for/user')
    

    Ok you pinky swear promised that the API returns an object with an id and a name. What if there's a bug? What if the API changes?

    Your code here takes your word for it. Sure it's a user with an id and a name. I'll behave as if that's true and we'll see what happens YOLO.

    Eventually you'll get an error like can't read property 'length' of undefined in some random place. Guess name can be undefined. Oops

    How run-time types could help

    Bun can run TypeScript directly. That means it still knows about your type annotations while running the code. At least in theory, I don't know the details of how Bun works.

    Imagine if Bun instead of ignoring your types at run-time, used them to validate your inputs! You'd write the same code as before and get a much more useful error.

    Say the API returns a malformed user. You could get a helpful message like Missing 'name' property on User at line code.ts:123.

    Or you could do polymorphism!

    type User {
    	id: number
    	name?: string
    }
    
    const user: User = await fetch('api/for/user')
    
    function printUser(user: { id: number }) {
    	console.log('user has id:', user.id)
    }
    
    function printUser(user: { id: number, name: string }) {
    	console.log('user', user.name, 'has id:', user.id)
    }
    

    Same function name, different implementation based on the type. Best used sparingly, but unlocks many code patterns that are super clunky to write in JavaScript.

    If you've ever seen code like this:

    catch (e) {
    	if (e.code === 123) {
    		//. ..
    	} else if (e.code) === 223) {
    		if (e.blah === 'omg') {
    			if (e.pleasestop === 'whyme') {
    				// ...
    			} else if (e.pleasestop === 'thissucks') {
    				// ...
    			}
    		}
    	}
    }
    

    Yes, I've seen that in production code. You do need it. Different errors require different cleanup, different error logging, different alerts. It's a mess.

    With run-time typing that could become:

    } catch (CommonError) {
    } catch (OtherError) {
    } catch (SuperWeirdError) {
    }
    

    But let's be honest that's a different language, not JavaScript with static types.

    Anyway ...

    Anyway that's the difference between a runtime that runs TypeScript code and having types at run-time. We'll never get JavaScript with run-time types because that would be a new language.

    But it's nice to dream.

    Cheers,
    ~Swizec

    Published on October 27th, 2023 in Reader question, TypeScript, Bun

    Did you enjoy this article?

    Continue reading about TypeScript runtime vs types at run-time

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

    Created by Swizec with ❤️