A type error can seem like an obstacle when you are trying to finish a program. It is also early feedback about a mistake. We'll see what a type checker does, how to run one in Python and TypeScript, how it helps an agent update callers, and which errors still need tests.
The examples in this video are in Python. The same ideas apply in other languages, and we will also see how to check TypeScript. Here is a small billing module. The function total takes a list of prices and adds them up. Somewhere else, somebody calls it with a single price instead of a list. Python is dynamically typed, which means the types of values are checked while the program runs. JavaScript, Ruby and many other languages work the same way. So when you run this file with Python, nothing complains about that call in advance. It fails the first time the line executes, and that may well be in production. Now run a static type checker on the same annotated Python. It reads the call before the program runs, sees that a single number is not a list of numbers, and reports an error. Nothing has run at that point. In a statically typed language, such as Java, this check is part of compiling the program. Python checks this operation when it executes. Adding a static type checker also lets you detect the mismatch before running the program.
The type checker is one of several checks that give feedback about a change. One difference between them is how long each takes to answer. A type checker analyzes the code without running the application. On a small project or an incremental run, it can report mismatches in under a second. A test suite executes the code to check particular behaviours. A small suite may take seconds, while a larger one can take minutes. Fast feedback lets the agent find and repair mistakes while the relevant change is still small. A one-second check is easy to run after each edit, and types, fast tests and linters can all contribute to that inner loop. Speed is not the main benefit of a type checker, though. On a large project, a full type run can take longer than a few quick tests. What the checker adds is coverage. It examines every call in the checked code, including calls on paths that no test executes.
Python gets static checking through gradual typing. Gradual typing means that typed and untyped code can be used together in one program, so you can add types to some parts while the rest stays untyped. In Python, those types are written as annotations. Here the parameter prices is annotated as a list of floats. Python does not enforce these type annotations when you call the function. Passing a single number still reaches the function, where summing it fails. The separate type checker analyzes the code and its annotations. It can follow the types through function calls without executing those calls. You can adopt checking a file at a time. By default, mypy skips bodies of functions without annotations. Strict mode checks those bodies and also reports missing annotations. Adding annotations gives it more precise types to check. TypeScript similarly adds static checking to JavaScript. Its type annotations are removed when JavaScript is emitted.
In Python, run mypy dash dash strict dot. Mypy has to be installed in the project's environment first, for example as a development dependency. The dot tells mypy to discover Python files under the current directory, with exclusions still applying. Mypy reports incompatible uses in the code it checks. Strict mode suits a project that is already annotated, like this billing module. In TypeScript, run npx tsc dash dash no-emit from a project where TypeScript is installed and configured. The no-emit flag checks the configured project without writing JavaScript. Strictness is a separate setting in that configuration. An error looks like this. A file, a line number, which argument is wrong, the type it had, and the type that was expected. That is useful feedback to an agent. It says what would have fit. Both commands analyze the selected project files without executing the application. That lets them report a mismatch even in a path you have not exercised with a test.
Suppose the agent changes a function's signature during a refactor. Here total gains a second parameter, the tax rate. Every existing call that passes only the prices no longer matches the new signature. The type checker reports the incompatible calls across the checked code in one pass. Here it finds two, one in the cart module and one in the invoice module. Each message names the file, the line and the missing argument. The agent gets a list of places to update without executing any of these calls. The agent adds a tax rate to the call in the cart module and runs the checker again. One error is left, and it points at the caller that still needs the same change. Without static checking, a mismatch may first appear when a test or a user runs that path. Calls you never exercise can remain undiscovered.
Annotations also record information for readers. The docstring says prices is a list of floats, but nothing checks that prose when the function changes. The annotation states that expected type in a form the checker uses. An incompatible operation in checked code produces an error instead of leaving only a stale sentence. Suppose total is changed to return the sum as formatted text, and its annotation now says it returns a string. A caller that still multiplies the total by a tax rate gets an error on the next type-checking run. If the return type were described only in a docstring, the same change would produce no message at all. A signature is documentation checked on each type-checking run. Within the checked code, incompatible uses produce failures that an ordinary docstring does not.
Python's type annotations can do more than say float or string. They can also give a value a name that says what it means. An x coordinate and a y coordinate are both floats, but mixing them up is a real bug. NewType creates a separate type for each one. At runtime each is still an ordinary float. The checker, though, treats X and Y as different types. So when a call passes y where x belongs, mypy reports it, even though both values are floats. The same works for a customer ID and an order ID that are both integers. A type alias gives a name to a larger type. Here a square is a pair of numbers, and a chess board is a dictionary from squares to pieces. With those names, the signature says that the function "piece at" takes a board and a square. It returns a piece, or None for an empty square. The vertical bar makes a union, a value that can be one type or the other. People who work on this code talk about boards and squares, not about dictionaries of pairs. The names let the code use the same words. A reader sees board and square, and never has to work out that a dictionary of pairs means a chess board. So, named types record the coder's intent. They tell a reader, and the agent, what each value stands for, and the checker keeps the code consistent with that intent.
Type feedback is also a reason to choose a typed language when working with agents. Studies of models writing TypeScript found that most of their compile errors were type errors. Those studies looked at code a model wrote in one go, before it ran any checker. An agent that runs the checker gets to see those errors and repair them before you do. Another option is to start with a typed language. GitHub reported that TypeScript became its most-used language by monthly contributors in August 2025. That report measures popularity, not the reasons for it. Static feedback is one reason a typed language is useful when working with agents. So a type checker catches most of the compile errors models make when they write typed code. It gives the agent concrete feedback to repair those errors without asking another model to judge the code.
The billing example also shows what types miss. Start with the contract. The function price after discount takes a price and a percentage, and it must return the final price after that percentage discount. Here is its implementation. We will try it with a price of one hundred and a twenty percent discount. It should become eighty. This implementation returns twenty, which is the discount amount rather than the price after the discount. mypy in strict mode is perfectly happy with this. Both arguments are floats, the return is a float, and every caller that treats the result as the final price gets the wrong number. So types check that values are used compatibly. Whether a value is the right one is a test's job. That is why type checks and behavioural tests complement each other.
So let the type checker take the first pass. It catches the mix-ups in seconds, and your tests are left to check that the answers are right. Thanks for watching, and I'll see you in the next video.