The Developer's Journey Through Type System Dogma
Why is this Languages meme funny?
Level 1: Simple Wisdom
Imagine you’re learning to cook. At first, you’re just a beginner, so you follow a simple recipe. You use basic ingredients – a pinch of salt, a bit of pepper – and the dish comes out pretty tasty. You think, "Hey, that works!" Now picture someone who’s gone to cooking school for a year: they look at your simple dish and go, “Oh no, that’s far too basic! You need to use a complex blend of exotic spices and a fancy plating technique, otherwise it’s all wrong!” This intermediate cook is overthinking it, trying to show off what they learned, and kind of missing the point. But then consider a master chef with 20 years of experience. The master chef smiles and also just uses salt, pepper, maybe a couple of herbs – the simple basics – because they know that often that’s all you need to make a meal great. In the end, both the beginner and the master chef are fine with the simple, straightforward method, while the intermediate cook is the one insisting it’s wrong just because it’s simple. This is funny because it shows that sometimes the more you really know, the more you appreciate simplicity. The beginner and the expert both agree on the easy approach, while the person with "just enough knowledge to be dangerous" is busy complicating things. In other words, keeping it simple can actually be the smartest thing to do!
Level 2: Type Checking 101
Let's break this meme down in plain terms. In programming, every piece of data has a type – essentially what kind of thing it is. For example, the number 42 is an integer type, "Hello" is a string type (text), and [1,2,3] might be a list/array type. Different languages handle types differently. Some are statically typed, meaning the type of each variable is checked and known at compile time (before the program runs). Examples include Java, C#, Rust, or Scala – these languages usually won't let you put a string where an int is expected without an explicit conversion, and they expect you to declare or infer what type each variable is. Others are dynamically typed, meaning a variable can hold any type of value at different times, and the actual type of the value is figured out while the program runs. Python, JavaScript, PHP, and Ruby are like this – you could have a variable that holds a number at one moment and later you assign a string to it, and that's fine. Now, comparing types means writing code that checks what type something is while the program is running. It's like asking a box, "Hey, what's inside you – a toy, a book, or a cake?" Many languages give you an operator or function for this. For instance, in Python you might do isinstance(x, int) to see if x is an integer. In JavaScript, you could write typeof value === 'number' to check if a value is a number. In Java or C#, you'd use instanceof or an is check, like if (obj instanceof Circle) { ... }. All these are ways of peeking at a variable and seeing "what kind of thing is this, exactly?"
Why would anyone be upset about doing that? This gets into ideas of code quality and design. A code smell is a term programmers use to describe something in code that might be a sign of a deeper problem – basically, it "smells funny." It's not necessarily a bug, but it's a hint that the code's design might not be ideal. Some people consider lots of type-checking to be a code smell. The reason is that in a well-designed program (especially in object-oriented programming), you shouldn't have to ask an object what type it is very often. Instead, you'd structure your code so that whatever needs to happen for that object is handled by the object itself or by polymorphism. For example, let's say you have an Animal class, and Dog and Cat are subclasses. Rather than writing code like if (animal is Dog) { animal.bark(); } else if (animal is Cat) { animal.meow(); }, you'd just call animal.speak() and let each subclass implement speak() in its own way. That way, the program doesn't need to check the type at runtime – it behaves correctly based on the actual type automatically. Writing a bunch of if/elif (or switch) statements to see what type something is and then handling each type manually is considered a more procedural or ad-hoc style. It's not wrong (the computer will do it just fine), but as projects grow bigger, that approach can become hard to maintain (every time you add a new animal type, you'd have to find all those type-checks and update them, for instance).
This is where Design Patterns and principles come in. Design patterns are basically tried-and-true solutions to common design problems. Our frustrated middle developer in the meme has learned some of these patterns and principles (like the SOLID principles of class design, though we don't need to go into what each letter means here). He's likely thinking, "Hey, if you need different behavior for different types, don't write an ugly if chain; use a pattern like Strategy or Visitor, or at least subclass and override methods!" To him, the code that explicitly checks types feels like someone cutting corners or not knowing "proper" OOP methodology. That's why he's yelling "Bad design patterns!!!" – he believes the code author is using an inferior solution when a cleaner pattern exists. "Code smell!!!" is basically him accusing the code of being suspicious or poorly structured.
On the flip side, the folks on the left and right of the meme are okay with type checks because of their perspective. In languages like Python or PHP, doing an isinstance check or examining type(obj) is pretty normal. Those languages are built around runtime type flexibility, so it's often practical to check types. For example, a Python function might want to handle strings differently from lists – it's perfectly fine for it to say if isinstance(data, list): ... else if isinstance(data, str): .... That doesn't make Python code "bad"; it's just a different style of programming where you rely on dynamic type info. The beginner developer in the meme, using these languages, hasn't been taught that there's anything wrong with that. They just want to get the job done in the simplest way.
Now, the expert developer on the right – the one using Scala with something fancy like ZIO – they have already gone through the phase of worrying about all these principles and patterns. They certainly know them inside-out. But they also know that real-world programming is messy and pragmatic. This person might be an architect or senior engineer who has seen projects where over-complicating things with too many patterns made the code worse, not better. So, this expert is comfortable saying, "Yes, I know checking the type directly is often frowned upon, but in this specific case, it's fine or even preferable." Perhaps in their Scala code, they use pattern matching (which, as we said, is essentially a controlled form of type checking) to handle different subtypes of a request or message. They don't consider that a sin; they consider it using the language's features intelligently. The meme is highlighting this contrast: the intermediate dev has a strict rulebook mindset ("Never do that!"), whereas the expert has a toolbox mindset ("Use whatever tool works best, even if it's a humble if statement sometimes").
In short, the phrase "only midwits hate simple type comparisons" is joking that it’s mostly the in-between folks who vehemently reject something as simple as checking a type. Beginners don't even know it's supposedly wrong – they just do it. True experts know it's not inherently wrong – it's just one tactic out of many, to be used when appropriate. But those who are in that middle learning stage often have just enough knowledge to be paranoid about certain practices but not enough experience to know when the rules can be bent. The bell-curve format of the meme humorously shows the beginner and expert agreeing (both saying "Comparing types is cool"), with the intermediate person in the middle freaking out about it. It’s a playful reminder in developer culture not to be that guy in the middle who uncritically labels everything a "code smell" or "bad practice" without context.
Level 3: Design Pattern Dogma
At the heart of this meme is a comedic portrayal of the IQ bell curve of developer mindsets. On the far left end of the curve, a happy-go-lucky junior developer (depicted as a simple smiling Wojak character) confidently says "Comparing types is cool." On the far right end, the hooded sage character (the meme's "Chad" Wojak representing enlightened wisdom) is also saying "Comparing types is cool." But smack in the middle, at the peak of the bell curve, we have a panicked intermediate developer (the crying "midwit" Wojak) absolutely losing it over the idea of doing type comparisons. He's shown alongside logos of C#, Java (classic OOP languages), and also Rust and Kotlin (more modern strictly-typed languages) – all languages known for encouraging strong type discipline and design patterns. This middle guy is frantically yelling complaints:
Mid-level Dev: "WTF, what do you do?! Bad design patterns!!! Code smell!!!"
The meme exaggerates, but it’s riffing on a real phenomenon. Why is the person in the middle so upset? Because as developers gain experience, they often learn the "proper" ways to write code – and then sometimes take those rules too literally. Our mid-level friend has recently internalized a bunch of principles about clean code and software architecture. One big lesson in object-oriented programming is: "Don't use explicit type checks (like instanceof); instead, use polymorphism to let objects figure out what to do." In plain terms, rather than writing code that says "if this object is of type A, do X, if it's type B, do Y," you would design your classes so that each class knows how to do the right thing (so you just call obj.doTheThing() and, thanks to polymorphism, the right version executes without any if). Checking an object's type directly is often cited as a code smell in OOP design – a sign that you might not have structured your classes well. The mid-level dev has also been reading up on design patterns (like those in the famous "Gang of Four" book) and principles like SOLID. To them, a piece of code that peeks at types feels like a broken design, a violation of the holy rules they've learned. So when this person sees code in a codebase that does something like:
if isinstance(shape, Circle):
draw_circle(shape)
elif isinstance(shape, Square):
draw_square(shape)
else:
draw_shape(shape)
they recoil in horror. In their mind, that logic should have been handled by the objects themselves – e.g., each shape class should just implement its own draw() method, so nobody outside needs to ask "are you a Circle or a Square?". The cry of "Bad design patterns!" from the Wojak means he thinks the coder is using a poor-man's solution instead of a proper pattern (perhaps they should have used the Visitor pattern or another design approach to handle new shapes more gracefully). And shouting "Code smell!" implies "this might work, but it feels wrong and could lead to problems later." In short, the mid-level dev is calling out what they perceive as an anti-pattern — something they were taught to avoid.
Now, the punchline is that both the beginner and the seasoned expert in this picture are totally okay with the exact thing that the intermediate dev is freaking out about. The beginner (maybe writing a quick script in PHP or Python) finds it perfectly sensible to just check types and handle things directly. Why not? It's simple and it works. They haven't been introduced to lofty design ideals yet, so an if based on type doesn’t keep them up at night. Meanwhile, the guru on the right (with the Scala logo, and even the icon of Scala's advanced effect library ZIO as a flex) has been through the entire cycle of hype and has come out the other side. This senior developer knows all about design patterns and code smells, but they've also seen the trap of over-engineering. They realize that writing five extra classes just to avoid one if statement might actually make the code harder to understand and maintain. In other words, the expert has the wisdom to sometimes break the "rules" when it makes practical sense. The expert might chuckle and say, "Actually, comparing types is cool, if you know what you're doing and the situation calls for it."
This dynamic plays out in real engineering decisions. Imagine a simple scenario: you have a user object and you want to do something special if the user is an admin. A junior might write: if user.role == "admin": allow_access(). Straightforward. An intermediate dev, freshly indoctrinated with design principles, might object: "Oof, a hardcoded role check? What if we need to support more roles later? This is a code smell!" They might propose a full refactor: make User an interface with subclasses like AdminUser and RegularUser, or use a Strategy pattern with separate classes for each role – all to avoid that one if check. That could be a fine design if the logic for roles is complex, but if not, it's overkill. Sometimes, adding all those classes and patterns is like using a chainsaw to cut butter: technically powerful, but unnecessarily messy. The meme definitely satirizes this tendency toward over-engineering in that middle phase of our careers. The mid-tier dev is essentially overreacting – he's so keen to demonstrate his knowledge (Rust! Kotlin! SOLID principles!) that he labels the simple solution as garbage. Ironically, a veteran might quietly prefer the simple solution in some cases because it's easier to read and plenty sufficient.
There's also an element of language community culture here. The grouping of logos isn't random. Java and C# are languages that historically put a big emphasis on object-oriented design, patterns, and enterprise-grade architecture – exactly the environment where people can get pedantic about "proper" structure. It's in such circles that you'll hear, "Never use instanceof, that's a red flag," and where tools might even warn you if you do a type cast or check. Rust and Kotlin are newer languages that attract developers who care about type safety and elegant design. Rustaceans, for instance, love that many errors are caught at compile time and might wrinkle their nose at the thought of doing something as dynamic as runtime type checks (though, interestingly, Rust uses match statements to great effect, which is just a structured form of type checking on enums). Kotlin, being a modern JVM language, encourages using its sealed classes and when expressions (pattern matching) for branching logic, which again feels more high-brow than an if/else chain. On the left side, we have PHP, Python, and (unpictured but implied) JavaScript – languages often used for quick scripting and web development, where being pragmatic and getting things done sometimes takes priority over abstract purity. In those, it's common to just check things at runtime because, well, you have to! There's no compiler catching type mistakes for you, so a simple typeof or instanceof is a handy tool. And Go (the little gopher icon) is an interesting inclusion on the left; Go is statically typed, but its philosophy is all about simplicity and practicality with minimal abstraction. Go programmers use interface types and sometimes do type assertions or type switches – and that's not frowned upon; it's idiomatic in Go to handle a value of interface type by checking what concrete type it actually holds. So the meme is also poking fun at language wars or elitism: one group screams "your approach is wrong and unclean!" while the other just shrugs and gets the job done.
For many of us who have been around in the field, this meme rings true and evokes a knowing smile. It captures what you might call the "horseshoe of wisdom" in programming: the novice and the expert, despite their vast difference in experience, sometimes arrive at similar conclusions, whereas the folks in between are off on a crusade of complexity. A classic scenario is how many programmers, after over-complicating a solution, eventually sigh and admit, "You know, a simple if-statement would have worked better here." It's not that the principles of good design are wrong — far from it — it's that real-world software is about balance and picking your battles. The intermediate dev in the meme is in that zealous phase of "No global variables! No hacks! Every piece of code must use the proper pattern!" The joke is that once you gain more experience, you learn where to apply the rules strictly and where you can be flexible. As a popular adage in software goes, Simple things should be simple, and complex things should be possible. The beginner and the master in this meme both embrace the simple thing (just check the type directly when needed), each for different reasons, while the mid-level dev will (hopefully) eventually learn that sometimes, keeping it simple is not a sin at all.
Level 4: Type Identity Crisis
In computer science theory, the notion of comparing types intersects with formal type systems and runtime introspection. In a pure static type system (as analyzed in type theory), types exist only at compile-time and are typically erased by the time the program runs. This means at runtime you normally cannot ask "what type is this?" because that information isn't usually carried with the data — unless the language provides extra metadata or runtime type information (RTTI). There's a fascinating tension here: parametric polymorphism (i.e. generics) and abstraction principles tell us that functions should not need to know the exact type of their inputs (in fact, Haskell's parametricity gives us "theorems for free" about functions that can't discriminate types). Yet, real-world programs occasionally do need to branch logic based on a value's type, so languages provide a kind of backdoor to inspect types when necessary (think of reflection in Java or type tags in dynamic languages).
Different languages handle this differently. Languages with nominal typing (like Java, C#, or Scala) attach a concrete identity to each type (usually a class or struct name), so an object often carries a pointer to some type descriptor. This makes it straightforward to ask at runtime, "Are you an instance of this class?" using operators like instanceof or methods like obj.getClass(). In contrast, languages with structural typing or duck typing (like Go's interface system or TypeScript) don't require an explicit type identity match; they care about what shape an object has (what methods or fields it has). Even then, those languages usually offer some way to check an underlying concrete type when needed (for example, Go has a type switch: switch x.(type) { case SomeType: ... } which is essentially performing a type comparison under the hood). And of course, in dynamic languages such as Python or JavaScript, every value knows its type at runtime (e.g., type(x) in Python, typeof x in JavaScript) because there's no compile-time enforcement – the program often must check "is this a list or a number?" on the fly.
Advanced type-happy languages like Scala blur the line in interesting ways. Scala has a powerful static type system, yet it encourages pattern matching on types of algebraic data types (ADTs). When you do a pattern match on, say, a sealed trait with multiple case classes, the Scala compiler generates type-checking code under the hood (essentially a series of instanceof checks and casts). But because it's baked into the language's pattern matching expression, it feels natural and is considered perfectly fine (not a code smell at all) in functional programming style. The enlightened Scala guru in the meme (with the Scala logo and the ZIO library icon, representing a master of advanced type-safe programming) knows that sometimes using the runtime type info is the most pragmatic solution. Interestingly, frameworks like ZIO leverage the type system heavily at compile-time to ensure correctness (for example, tracking effects), yet even those advanced libraries may rely on a bit of runtime type magic (like tagging or type classes) to make things work. The takeaway is that at the highest levels of abstraction, you're not forbidden from using type information – you just use it in a more principled or clever way.
From a lofty perspective, theorists and seasoned language designers understand that forbidding all runtime type checks is more of a guideline than an absolute law. It's reminiscent of the Curry-Howard correspondence idea where types are like propositions in logic: in a pure logical system you wouldn't normally ask "what proposition is this proof for?" (analogous to asking an object its type), yet when implementing these systems, reflection and introspection techniques become necessary to interface with the real world. The expert developer isn't betraying type theory by occasionally checking types; they're exercising wisdom in bridging theoretical purity with practical needs. It's almost poetic: the lowest-level programmer and the highest-level programming sage both engage in what we can call type introspection – one out of simplicity (or ignorance of "better" patterns), and the other out of deep understanding of the language's type system and knowing when a little escape hatch is okay. Meanwhile, the rule-bound intermediate folks in the middle see any such escape as heresy. This contrast sets the stage for the more human side of the joke in the meme.
Description
A bell curve meme, also known as the IQ distribution meme, illustrating the evolution of a programmer's perspective on type systems. On the far left, a low-IQ Wojak character, representing a beginner, is surrounded by logos for dynamically-typed languages (PHP, Go, Python, JS) and thinks, 'Comparing types is cool,' signifying a naive acceptance of type coercion. In the center, at the peak of the curve, a distressed mid-IQ character, representing a mid-level developer, is surrounded by statically-typed languages (C#, Java, Rust, Kotlin) and panics about 'Bad design patterns!!!' and 'Code smell!!!', reflecting a rigid, dogmatic phase. On the far right, an enlightened, high-IQ character in a hood is shown with logos for Zig and another language, returning to the same conclusion, 'Comparing types is cool.' This illustrates the cycle of learning where experts regain a pragmatic appreciation for flexibility and simplicity after moving past the prescriptive rules that dominate the intermediate stage
Comments
14Comment deleted
The junior ships it because `5 == '5'`. The mid-level dev writes a 300-line abstract factory to prevent it. The senior dev ships it because they know the input is a validated string from a trusted source
The real bell curve: the intern writes `if (x is String)`, the mid-level architect drafts a Visitor over a hand-rolled type lattice, and the distinguished engineer quietly merges `if (x is String)` while Slack is still arguing about SOLID
The real 200 IQ move is realizing that after 20 years in the industry, you'll still be debugging '1' + 1 === '11' in production at 3 AM, regardless of whether you wrote a 10-page design document about type safety or just shipped it with a TODO comment
The midwit spent five years building a phantom-typed, GoF-certified abstraction so no one could ever compare types; the Zig guy just compared them, shipped, and went home. Both tails sleep fine - only the middle of the curve has a linter for its dreams
This meme perfectly captures the horseshoe theory of type systems: junior devs happily use dynamic typing without worry, mid-level devs rage against weakly-typed codebases they inherit, and senior architects circle back to appreciating structural typing - having seen enough enterprise Java generics hell to know that nominal type systems can be just as painful as no types at all. The real wisdom isn't in the type system itself, but knowing when `isinstance()` checks are actually more maintainable than a 47-line generic constraint
The middle of the bell writes a VisitorFactoryStrategy to avoid if (typeof x === 'string'); the monk deletes it with a sealed ADT and exhaustiveness checks - guess whose on‑call stays quiet
Java's 34% dominance: proof that boilerplate scales to enterprise, while TS's 2% quietly prevents the NullPointerExceptions Java invented
Bell curve of type checks: “typeof,” “code smell - wrap it in a StrategyFactory,” “make the invalid state unrepresentable so the compiler removes the branch.”
What are languages on the right? Comment deleted
The one with letter z is zig Comment deleted
I've looked through like 5 lists of programming language icons and I cannot find it Comment deleted
Zig and Idris Comment deleted
The red one Comment deleted
Source https://hirrolot.github.io/posts/why-static-languages-suffer-from-complexity Comment deleted