Hello, I’m Masa. Are you using AI for coding?
I recently spent some time organising my thoughts on TypeScript and Type-Driven Development in the age of AI, so I decided to write them down as a kind of thinking memo. This article reflects my current hypotheses and understanding, rather than a fully formed conclusion. It is simply an attempt to organise my thoughts at this point in time.
What is Type-Driven Development?
Put simply, Type-Driven Development is an approach where data structures and function interfaces are defined in advance before implementing the actual program logic, using languages such as TypeScript. By repeatedly refining these definitions and relying on compiler checks, this approach aims to eliminate potential sources of bugs as early as possible and to build safer and more robust code from the outset.
Why Type-Driven Development in the age of AI?
How we got here
Development using generative AI appears to have evolved through several stages, moving from code generation, to instruction optimisation, and then to the control of broader context and constraints. Today, development with generative AI tools such as Cursor, Windsurf, Junie, and Antigravity has become common.
At a basic level, this is often referred to as vibe coding. As these approaches become more refined, they are sometimes described as AI orchestration or prompt driven development. Across all of these approaches, the primary concern is how to ensure the quality of code generated by AI.
With vague instructions, AI tends to generate code that simply works, but is inefficient and difficult to maintain, with poor performance characteristics. Prompt engineering emerged as a way to mitigate this, but it requires developers to provide increasingly detailed instructions each time.
To address this, the idea of context engineering gained traction, along with mechanisms such as AGENTS.md to communicate broader intentions and constraints to large language models. This has enabled developers to exert greater control over AI behaviour, and as a result the overall quality of generated code has improved significantly..
Even so, breakdowns still occur
That said, AGENTS.md does not completely prevent deviations from specification. As projects grow larger and involve more people, breakdowns possibly occur.
Developers then find themselves repeatedly editing AGENTS.md or painstakingly reading and correcting AI generated code. While operational practices and code reviews can mitigate these issues to some extent, the associated costs tend to grow proportionally as the project scales.
Type definitions can also become problematic. With poorly framed instructions, AI may introduce excessive use of any or unknown, or generate overly complex types in situations where they are unnecessary. While the quality of generated output has improved, AGENTS.md is not a perfect solution.
AGENTS.md exists to describe what we want AI to build. To ensure quality at a system level, an additional layer of control is still required.
This is where Type-Driven Development comes in
By defining the expected types on the human side first, we can require AI to generate code based on those assumptions. If an implementation becomes impossible due to the existing types, the AI can propose extensions, which humans can then evaluate and incorporate into the type definitions.
If the AI generated code deviates from the defined types, TypeScript will reject it. Once rejected, the AI is forced into a correction loop. This helps maintain consistency in generated code and reduces the amount of manual rework required later.
In this sense, it can be seen as entering into a contract with AI where code that breaks the rules defined by types is not accepted. At the same time, responsibility remains firmly on the human side.
Of course, types do not guarantee logical correctness or performance by themselves. Their primary role is to enforce structure, boundaries, and consistency. This makes the developer’s design skills even more important.
Types are not generated artefacts. They function as rules that govern AI behaviour.
Paying the cost upfront
One reason to adopt Type-Driven Development is to avoid deferring technical debt. This does not mean that every type must be defined manually, but delegating too much type design to AI often results in higher costs when changes are required later.
Type-Driven Development can be seen as paying the cost upfront in order to eliminate deferred costs. As mentioned earlier, AI can suggest changes when necessary, but final decisions should not be delegated. Design and responsibility belong to the developer, and type definitions represent that upfront commitment.
A clear division of roles with context engineering and AGENTS.md
- AGENTS.md and context define goals, policies, constraints, and procedures through written rules.
- Types as contracts define structure, boundaries, and consistency, enforced by the compiler.
Where context alone cannot fully constrain behaviour, types provide an additional layer of enforcement at the compiler level, closing potential gaps.
The initial investment is higher
Designing and defining types by hand certainly requires additional effort at the beginning. However, this effort is an investment that reduces future breakdowns, rework, and reactive fixes.
As projects grow, this approach tends to produce codebases that scale more smoothly and are easier to maintain. Handing work to AI is easy, but ensuring quality after the fact can be significantly more costly.
Beyond vibe coding
I do not believe I have fully articulated all of this yet, but I see context engineering, AGENTS.md, Type-Driven Development, and testing as tools with different control points.
The challenge lies in how these tools are combined and applied effectively. We now live in a time where development without AI is almost unthinkable. Moving beyond simply using AI for vibe coding, the goal should be to become developers who can harness AI in a controlled and deliberate way, continually refining our approach and increasing our value in the job market.
That’s all for now.
Cheers.
