How I Navigate Multiple Programming Languages as a Pragmatic Developer
Starting with Java for Android development in university, I’ve since worked professionally with Python, TypeScript, Elixir, Ruby, Clojure, Kotlin, and NestJS over the past six years. This wasn’t planned—each language came from real project needs. What I’ve learned isn’t just syntax, but how to quickly understand what makes each language tick.
My Journey Through Languages
Java/Android (PayTime, 2017-2018)
My programming journey started with Android development in Java. As part of a university entrepreneurship program, I built the complete Android client for PayTime—a talent-trading platform. I implemented MVP architecture, real-time chat, SNS login, and payment integration. The project won an excellence award.
Java taught me object-oriented fundamentals, but more importantly, it was my first experience building a real product that users would actually touch. The gap between tutorial code and production code became immediately clear.
Python (SSG.COM, 2019)
I built REST APIs with Flask for AI model inference and labelling. Python taught me the basics—but more importantly, it showed me that a language’s ecosystem matters as much as its syntax. The data science libraries were why we chose Python, not the language itself.
TypeScript/React (Kakao, 2020)
I joined Kakao as a frontend developer building the KakaoWork Admin interface. TypeScript’s type system was my first encounter with static typing in a dynamically-typed ecosystem.
The reality was harder than expected. Achieving perfect type coverage turned out to be surprisingly difficult. Simple logic flows sometimes required unnecessary abstractions or intermediate conversion steps just to satisfy the type checker. And even with all that effort, types didn’t catch every bug, they gave a false sense of security at times.
Elixir/Phoenix (Kakao and Ringle, 2020-2024)
This was my turning point. When our team needed backend developers, I volunteered to learn Elixir. Coming from JavaScript, the functional paradigm was initially foreign—immutability, pattern matching, pipe operators.
But once it clicked, I realized functional programming wasn’t just a different syntax. It was a different way of thinking about data flow. Pattern matching replaced conditional chains. Immutability eliminated entire categories of bugs. The pipe operator made transformations readable.
What made Elixir truly special was its pragmatic approach to reliability. The “Let it Crash” philosophy sounds reckless at first, but in practice it was liberating. Instead of writing defensive code for every edge case, I could let processes fail and trust the supervisor to restart them cleanly. This wasn’t laziness—it was a fundamentally different way to handle failure that resulted in simpler, more robust systems.
The most unexpectedly useful feature in production was hot code swapping. You can connect to a running server remotely and modify code in real-time. Yes, it’s risky. Yes, for stability you should avoid it. But here’s my honest opinion: if you’re not a perfectionist, the temptation is hard to resist. Why wait through long, tedious build and deploy cycles when you can fix a bug instantly, verify the result in production, and then leisurely submit your code for review? It’s dangerous, but it’s also incredibly practical when used carefully.
Within three months, I was delivering production GraphQL and gRPC services.
Ruby on Rails (Ringle, 2024)
After Elixir, Ruby felt familiar yet different. Both emphasize developer happiness, but in different ways. Ruby’s “magic”—metaprogramming, implicit behaviors, convention over configuration—was the opposite of Elixir’s explicitness.
The magic was also the biggest challenge. Understanding legacy Rails code was extremely difficult because so much happens implicitly. Variables update without visible assignments. Methods appear from nowhere through metaprogramming. Even AI assistants frequently misunderstood the code, hallucinating behaviors that didn’t exist. I had to trace through the codebase manually to understand what was actually happening.
Clojure (StudioXID, 2024-2025)
Inheriting a legacy Clojure service, I leveraged my Elixir background. Both are functional—immutability, data transformation pipelines, the emphasis on pure functions. The concepts transferred directly.
What differed was the Lisp heritage: the REPL-driven development, the homoiconicity, the minimal syntax. I learned Clojure not by studying tutorials, but by reading production code and making incremental improvements.
The biggest pain point was the ecosystem. Solving real problems often required falling back to Java libraries. Pure Clojure solutions were sometimes unavailable or poorly maintained. The language itself was elegant, but the practical reality meant constantly bridging into Java land.
Kotlin (StudioXID, 2025)
Rebuilding a service with clean architecture, I approached Kotlin through its functional features—null safety, data classes, extension functions. Coming from Elixir and Clojure, I focused on what Kotlin borrowed from functional programming rather than its Java heritage.
TDD guided my learning. Writing tests first helped me understand Kotlin idioms before I fully grasped the language.
But like Clojure, the ecosystem disappointed me. Kotlin-native libraries were often immature or missing. Most of the time, I ended up using Java libraries anyway, which made me question whether Kotlin was worth the added complexity. Sometimes I wondered if just writing Java would have been simpler.
NestJS/TypeScript (StudioXID, 2025-Present)
Back to TypeScript, but now for enterprise backend with DDD and CQRS patterns. NestJS’s decorator-based architecture felt familiar from my understanding of clean architecture principles. The language was secondary to the patterns.
What I’ve Learned About Learning Languages
Every Language Has a Core Philosophy
Languages aren’t just different syntax for the same ideas. Each has a philosophy:
- Elixir: Explicit data transformation, fault tolerance through supervision
- Ruby: Developer happiness, expressive code, convention over configuration
- Clojure: Data-oriented programming, simplicity through minimal syntax
- Kotlin: Safety without verbosity, pragmatic functional features
- TypeScript: Type safety retrofitted onto JavaScript’s flexibility
Understanding this philosophy first makes everything else click faster.
Find the Language’s Signature Patterns
Every language has patterns that feel natural only in that language:
- Elixir’s pipe operator and pattern matching
- Ruby’s blocks and metaprogramming
- Clojure’s threading macros and persistent data structures
- Kotlin’s extension functions and sealed classes
I look for these signature patterns early. They’re how experienced developers write idiomatic code.
Concepts Transfer, Syntax Doesn’t
My Elixir experience made Clojure approachable because both share functional concepts. My understanding of clean architecture made NestJS patterns familiar regardless of the TypeScript specifics.
I invest in understanding concepts deeply. They pay dividends across every language.
Learn Through Production Work
I don’t wait until I “know” a language to contribute. The fastest path to competence is:
- Understand fundamentals quickly (a few days to a week)
- Start with small features immediately
- Read existing code to learn patterns
- Pair with experienced developers during reviews
- Document what I learn for others
Tutorials teach syntax. Production work teaches engineering judgment.
The Pragmatic Approach
I’m not attached to any particular language. Each has real trade-offs that I’ve experienced firsthand:
- Elixir: Excellent concurrency and fault tolerance, but smaller ecosystem and hiring pool
- Ruby on Rails: Rapid development, but magic makes legacy code nearly impossible to understand
- TypeScript: Promises type safety, but achieving full coverage requires painful abstractions
- Clojure: Elegant language, but you’ll end up in Java land for most practical problems
- Kotlin: Modern syntax improvements over Java, but the ecosystem often makes you wonder why not just use Java
The right language depends on the problem, the team, and the constraints. My job is to be effective in whatever language the situation demands—and to quickly identify how to be effective in it.
What This Means for My Career
Being a polyglot developer isn’t about collecting languages. It’s about:
- Rapid adaptation: I can contribute to new codebases quickly
- Pattern recognition: I see similarities across different ecosystems
- Pragmatic choices: I understand trade-offs, not just preferences
- Continuous learning: Each new language refines my understanding of programming itself
After six years and seven languages, I’m confident I can pick up any language a team needs. Not because I’m special, but because I’ve developed a systematic approach to understanding what makes each language unique and how to leverage that understanding quickly.
The best programmers I know aren’t defined by their favorite language. They’re defined by their ability to solve problems with whatever tools are available.