TypeScript

Structural Typing vs. Nominal Typing: TypeScript and Java Compared

Same interface, two unrelated types, two completely different rules about what counts as a match.

Imagine an interface called Movable, with nothing more exotic than an x coordinate, a y coordinate, and a move operation. Now imagine two classes that have never heard of each other: a Drone and a BoardGamePiece. Both happen to expose an x, a y, and a move method. In TypeScript, that coincidence is enough; both classes satisfy Movable without a single line announcing it. In Java, that same coincidence means nothing at all until one of the classes explicitly writes the words “implements Movable.” That one difference, whether the compiler trusts a declared name or the actual shape of the data, is the entire dividing line between nominal and structural typing, and it quietly shapes how comfortable refactoring feels in each language.

Nominal Typing: Java Trusts the Label, Not the Shape

Java’s type system is nominal, meaning type compatibility is decided by declared relationships, not by comparing members side by side. A class is only considered an instance of an interface if it, or one of its ancestors, explicitly says so with an implements or extends clause. This is spelled out plainly in the Java Language Specification’s chapter on classes and the companion chapter on interfaces: subtyping is a relation the programmer declares, and the compiler simply checks that the declaration holds.

Going back to the running example, the Drone class does not accidentally satisfy Movable in Java just because it has matching fields and a matching method signature. Unless the class declaration reads “class Drone implements Movable,” the compiler treats Drone and Movable as entirely unrelated types, even if every member lines up perfectly. That might feel rigid, but it buys something real: a Java interface functions as a promise the author made on purpose, not a pattern the compiler noticed by accident.

What this costs you

The price of nominal typing shows up whenever you want to unify two pre-existing classes under a shared abstraction after the fact. If Drone and BoardGamePiece were written independently and neither declares Movable, no code anywhere can treat them interchangeably as Movable, no matter how identical their shapes are, until someone edits at least one of the class declarations or introduces an adapter. Retrofitting a shared interface onto existing code is therefore always an explicit, visible change in Java, never a silent one.

Structural Typing: TypeScript Trusts the Shape

TypeScript flips that logic entirely, formalizing the duck-typing habit JavaScript developers already had. Its type system, described in detail in the TypeScript Handbook’s chapter on object types, decides compatibility by comparing the members two types expose, not by checking whether either type declares a relationship to the other. If Drone has an x, a y, and a move method with a compatible signature, TypeScript considers it assignable to Movable automatically, even if the word Movable never appears anywhere near the Drone class.

This is precisely what most people mean when they say TypeScript formalizes duck typing: if it walks like a duck and quacks like a duck, TypeScript is satisfied, regardless of whether anything ever called it a duck. For the running example, both Drone and BoardGamePiece slot into any function that expects a Movable without either class needing to know Movable exists.

The one place structural typing gets stricter: excess property checks

Structural typing has one deliberate wrinkle worth knowing, because it trips people up constantly. When you hand TypeScript a freshly written object literal directly, rather than a variable, it performs what the Handbook calls an excess property check and will flag extra properties that were not part of the target shape. Assign that same literal to a variable first, and the extra properties are suddenly fine. The underlying compatibility rule never changes; TypeScript is simply guessing that a literal typo is more likely to be a mistake than an intentional widening, so it tightens the check at exactly the moment you are least likely to have meant it.

Side by Side: The Same Question, Answered Differently

AspectJava (Nominal)TypeScript (Structural)
Basis for compatibilityDeclared relationship (implements/extends)Matching members and signatures
Two unrelated classes with identical shapesNot interchangeable unless both declare the interfaceInterchangeable automatically
Retrofitting a shared interface onto old codeRequires editing the class declarationRequires nothing; compatibility already exists
Risk of accidental compatibilityVery low; every match is intentionalPossible with coincidentally similar shapes
Object literal edge caseNot applicableExcess property checks tighten literal assignment
Typical mental model“What was this declared to be?”“What does this actually look like right now?”

Why the Difference Isn’t Just Academic

Nominal typing makes an interface feel like a contract with an author behind it. When a Java interface changes, every implementing class was, by definition, written with awareness that the interface existed, so the blast radius of a breaking change is at least discoverable through the type hierarchy. Structural typing removes that safety net but replaces it with something JavaScript developers already valued: the ability to write a function once and have it accept anything close enough in shape, without asking any existing code to change or even to know the function exists.

The trade-off becomes concrete in large codebases. A Java team introducing a new interface across dozens of pre-existing classes has to touch every one of those class declarations, a mechanical but visible refactor. A TypeScript team introducing the equivalent interface touches nothing except the new function signature; every object that already has the right shape starts satisfying it immediately, for better and for worse. That same convenience is also why TypeScript, despite compiling to loosely typed JavaScript underneath, still manages to catch a large share of the shape mismatches that historically caused runtime errors in plain JavaScript, and why it climbed from twelfth place in the 2018 Stack Overflow survey’s usage rankings to the top five within a few years, a rise The Register covered in its writeup of the 2022 results.

What the Developer Data Shows

It helps to put actual numbers next to the theory. The 2024 Stack Overflow Developer Survey asked the same two questions of both languages: how many developers used each one extensively over the past year, and, of those, how many want to keep using it.

Extensive Use Over the Past Year, 2024

Share of all survey respondents who reported extensive development work in the language.Source: Stack Overflow 2024 Developer Survey, Technology section

TypeScript’s structural checks apparently do not slow teams down enough to dent adoption; it is used more extensively than Java among 2024 respondents, a notable fact given that Java has decades of enterprise entrenchment behind it and TypeScript is a comparatively recent addition to most stacks.

Admired Score by Language, 2024

Share of developers who used the language this year and want to keep using it next year.Source: Stack Overflow 2024 Developer Survey, Technology section

The admiration gap is even wider than the usage gap. Structural typing’s flexibility, plus the safety net TypeScript adds on top of plain JavaScript, seems to translate directly into developers wanting to keep working with it, while Java’s admiration score trails noticeably despite its far longer track record and its enormous existing install base.

Quick Checklist: Spotting Which Typing Model You’re Reasoning About

  • Ask whether a matching shape alone is enough, or whether a declared relationship is required first.
  • Check whether adding a new shared interface to old code means editing the old code or means nothing at all.
  • Watch for TypeScript’s excess property check specifically around freshly written object literals, since it is the one place structural rules briefly act nominal.
  • Remember that nominal typing makes breaking changes discoverable through declared relationships, while structural typing makes them discoverable only by re-checking every shape that happens to match.
  • When retrofitting an abstraction onto existing classes, expect a mechanical edit in Java and expect nothing at all in TypeScript.

What We Have Learned

Java and TypeScript answer the exact same question, “does this type satisfy that interface,” in opposite ways. Java checks a declared relationship and ignores shape entirely, which means an interface is always an intentional, discoverable contract, at the cost of needing explicit edits whenever old code needs to join a new abstraction. TypeScript checks the shape directly and ignores declared relationships, formalizing the duck typing JavaScript developers already relied on, with the one notable exception of excess property checks on fresh object literals.

Neither model is a mistake the other language failed to make; each is a deliberate bet about whether safety should come from declared intent or from flexible structural matching. The 2024 developer survey data suggests that bet is paying off for TypeScript specifically, since it now sees both higher extensive usage and a meaningfully higher admiration score than Java, even though Java’s nominal contracts remain the safer choice for large, long-lived interface hierarchies where discoverability matters most.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button